From f3097718606c392d76754922bad1bab76ca54681 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Wed, 8 Mar 2023 15:58:23 -0500 Subject: [PATCH 01/18] Model readyPromise deprecation and minor loader refactor --- Specs/loaderProcess.js | 3 +- Specs/waitForLoaderProcess.js | 24 +- .../Source/DataSources/ModelVisualizer.js | 79 +- packages/engine/Source/Scene/BufferLoader.js | 61 +- .../Scene/Cesium3DTileContentFactory.js | 2 + .../Scene/Cesium3DTilesVoxelProvider.js | 7 +- .../engine/Source/Scene/Cesium3DTileset.js | 6 +- .../Source/Scene/GltfBufferViewLoader.js | 127 +- .../engine/Source/Scene/GltfDracoLoader.js | 205 +- .../engine/Source/Scene/GltfImageLoader.js | 144 +- .../Source/Scene/GltfIndexBufferLoader.js | 223 +- .../engine/Source/Scene/GltfJsonLoader.js | 145 +- packages/engine/Source/Scene/GltfLoader.js | 586 ++-- .../Scene/GltfStructuralMetadataLoader.js | 203 +- .../engine/Source/Scene/GltfTextureLoader.js | 174 +- .../Source/Scene/GltfVertexBufferLoader.js | 256 +- .../engine/Source/Scene/ImplicitSubtree.js | 4 +- .../Source/Scene/MetadataSchemaLoader.js | 62 +- packages/engine/Source/Scene/Model/Model.js | 528 +++- .../Source/Scene/Model/Model3DTileContent.js | 25 +- .../Scene/Model/ModelAnimationCollection.js | 4 +- .../engine/Source/Scene/Model/ModelUtility.js | 32 +- packages/engine/Source/Scene/ResourceCache.js | 152 +- .../Source/Scene/ResourceCacheStatistics.js | 83 +- .../engine/Source/Scene/ResourceLoader.js | 22 +- .../Source/Scene/ResourceLoaderState.js | 16 +- .../Specs/DataSources/ModelVisualizerSpec.js | 203 +- .../engine/Specs/Scene/BufferLoaderSpec.js | 100 +- .../Scene/GlobeSurfaceTileProviderSpec.js | 4 +- .../Specs/Scene/GltfBufferViewLoaderSpec.js | 143 +- .../engine/Specs/Scene/GltfDracoLoaderSpec.js | 129 +- .../engine/Specs/Scene/GltfImageLoaderSpec.js | 271 +- .../Specs/Scene/GltfIndexBufferLoaderSpec.js | 260 +- .../engine/Specs/Scene/GltfJsonLoaderSpec.js | 216 +- packages/engine/Specs/Scene/GltfLoaderSpec.js | 264 +- .../Scene/GltfStructuralMetadataLoaderSpec.js | 274 +- .../Specs/Scene/GltfTextureLoaderSpec.js | 216 +- .../Specs/Scene/GltfVertexBufferLoaderSpec.js | 302 +- .../Specs/Scene/MetadataSchemaLoaderSpec.js | 95 +- .../Model/ModelAnimationCollectionSpec.js | 80 +- .../Scene/Model/ModelMatrixUpdateStageSpec.js | 12 +- .../Specs/Scene/Model/ModelSceneGraphSpec.js | 102 +- .../engine/Specs/Scene/Model/ModelSpec.js | 714 +++-- .../Specs/Scene/Model/loadAndZoomToModel.js | 86 - .../Scene/Model/loadAndZoomToModelAsync.js | 67 + .../Scene/PostProcessStageLibrarySpec.js | 8 +- .../Specs/Scene/PostProcessStageSpec.js | 4 +- .../engine/Specs/Scene/ResourceCacheSpec.js | 2574 +++++++---------- packages/engine/Specs/Scene/ShadowMapSpec.js | 11 +- 49 files changed, 4372 insertions(+), 4936 deletions(-) delete mode 100644 packages/engine/Specs/Scene/Model/loadAndZoomToModel.js create mode 100644 packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js diff --git a/Specs/loaderProcess.js b/Specs/loaderProcess.js index d91ca294d4bc..5953cb7dcf7c 100644 --- a/Specs/loaderProcess.js +++ b/Specs/loaderProcess.js @@ -4,8 +4,9 @@ function loaderProcess(loader, scene) { // explicitly. This is only required for loaders that use the job scheduler // like GltfVertexBufferLoader, GltfIndexBufferLoader, and GltfTextureLoader scene.jobScheduler.resetBudgets(); - loader.process(scene.frameState); + const ready = loader.process(scene.frameState); scene.jobScheduler.resetBudgets(); + return ready; } export default loaderProcess; diff --git a/Specs/waitForLoaderProcess.js b/Specs/waitForLoaderProcess.js index 737fa7c6771a..d46cc9ed3c09 100644 --- a/Specs/waitForLoaderProcess.js +++ b/Specs/waitForLoaderProcess.js @@ -2,26 +2,12 @@ import loaderProcess from "./loaderProcess.js"; import pollToPromise from "./pollToPromise.js"; function waitForLoaderProcess(loader, scene) { - return new Promise(function (resolve, reject) { - let loaderFinished = false; + return pollToPromise(function () { + if (loader.isDestroyed()) { + return true; + } - pollToPromise(function () { - loaderProcess(loader, scene); - return loaderFinished; - }).catch(function (e) { - reject(e); - }); - - loader.promise - .then(function (result) { - resolve(result); - }) - .catch(function (e) { - reject(e); - }) - .finally(function () { - loaderFinished = true; - }); + return loaderProcess(loader, scene); }); } diff --git a/packages/engine/Source/DataSources/ModelVisualizer.js b/packages/engine/Source/DataSources/ModelVisualizer.js index b86c061ffc55..9c2a49b1d291 100644 --- a/packages/engine/Source/DataSources/ModelVisualizer.js +++ b/packages/engine/Source/DataSources/ModelVisualizer.js @@ -102,45 +102,72 @@ ModelVisualizer.prototype.update = function (time) { } if (!show) { - if (defined(modelData)) { + if (defined(modelData) && modelData.modelPrimitive) { modelData.modelPrimitive.show = false; } continue; } - let model = defined(modelData) ? modelData.modelPrimitive : undefined; - if (!defined(model) || resource.url !== modelData.url) { - if (defined(model)) { - primitives.removeAndDestroy(model); + if (!defined(modelData) || resource.url !== modelData.url) { + if (defined(modelData?.modelPrimitive)) { + primitives.removeAndDestroy(modelData.modelPrimitive); delete modelHash[entity.id]; } - model = Model.fromGltf({ - url: resource, - incrementallyLoadTextures: Property.getValueOrDefault( - modelGraphics._incrementallyLoadTextures, - time, - defaultIncrementallyLoadTextures - ), - scene: this._scene, - }); - model.id = entity; - primitives.add(model); - modelData = { - modelPrimitive: model, + modelPrimitive: undefined, url: resource.url, animationsRunning: false, nodeTransformationsScratch: {}, articulationsScratch: {}, - loadFail: false, + loadFailed: false, awaitingSampleTerrain: false, clampedBoundingSphere: undefined, sampleTerrainFailed: false, }; modelHash[entity.id] = modelData; - checkModelLoad(model, entity, modelHash); + (async () => { + try { + const model = await Model.fromGltfAsync({ + url: resource, + incrementallyLoadTextures: Property.getValueOrDefault( + modelGraphics._incrementallyLoadTextures, + time, + defaultIncrementallyLoadTextures + ), + scene: this._scene, + }); + model.id = entity; + primitives.add(model); + modelHash[entity.id].modelPrimitive = model; + model.errorEvent.addEventListener((error) => { + if (!defined(modelHash[entity.id])) { + return; + } + + console.error(error); + + // Texture failures when incrementallyLoadTextures + // will not affect the ability to compute the bounding sphere + if (error.name !== "TextureError") { + modelHash[entity.id].loadFailed = true; + } + }); + } catch (error) { + if (!defined(modelHash[entity.id])) { + return; + } + + console.error(error); + modelHash[entity.id].loadFailed = true; + } + })(); + } + + const model = modelData.modelPrimitive; + if (!defined(model)) { + continue; } model.show = true; @@ -365,7 +392,11 @@ ModelVisualizer.prototype.getBoundingSphere = function (entity, result) { //>>includeEnd('debug'); const modelData = this._modelHash[entity.id]; - if (!defined(modelData) || modelData.loadFail) { + if (!defined(modelData)) { + return BoundingSphereState.PENDING; + } + + if (modelData.loadFailed) { return BoundingSphereState.FAILED; } @@ -539,10 +570,4 @@ function clearNodeTransformationsArticulationsScratch(entity, modelHash) { } } -function checkModelLoad(model, entity, modelHash) { - model.readyPromise.catch(function (error) { - console.error(error); - modelHash[entity.id].loadFail = true; - }); -} export default ModelVisualizer; diff --git a/packages/engine/Source/Scene/BufferLoader.js b/packages/engine/Source/Scene/BufferLoader.js index f04cd75ad194..2246f485ac63 100644 --- a/packages/engine/Source/Scene/BufferLoader.js +++ b/packages/engine/Source/Scene/BufferLoader.js @@ -50,19 +50,6 @@ if (defined(Object.create)) { } Object.defineProperties(BufferLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. - * - * @memberof BufferLoader.prototype - * - * @type {Promise.|undefined} - * @readonly - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -96,35 +83,41 @@ Object.defineProperties(BufferLoader.prototype, { * @returns {Promise.} A promise which resolves to the loader when the resource loading is completed. * @private */ -BufferLoader.prototype.load = function () { +BufferLoader.prototype.load = async function () { + if (defined(this._promise)) { + return this._promise; + } + if (defined(this._typedArray)) { this._promise = Promise.resolve(this); - } else { - this._promise = loadExternalBuffer(this); + return this._promise; } + + this._promise = loadExternalBuffer(this); return this._promise; }; -function loadExternalBuffer(bufferLoader) { +async function loadExternalBuffer(bufferLoader) { const resource = bufferLoader._resource; bufferLoader._state = ResourceLoaderState.LOADING; - return BufferLoader._fetchArrayBuffer(resource) - .then(function (arrayBuffer) { - if (bufferLoader.isDestroyed()) { - return; - } - bufferLoader._typedArray = new Uint8Array(arrayBuffer); - bufferLoader._state = ResourceLoaderState.READY; - return bufferLoader; - }) - .catch(function (error) { - if (bufferLoader.isDestroyed()) { - return; - } - bufferLoader._state = ResourceLoaderState.FAILED; - const errorMessage = `Failed to load external buffer: ${resource.url}`; - return Promise.reject(bufferLoader.getError(errorMessage, error)); - }); + try { + const arrayBuffer = await BufferLoader._fetchArrayBuffer(resource); + if (bufferLoader.isDestroyed()) { + return; + } + + bufferLoader._typedArray = new Uint8Array(arrayBuffer); + bufferLoader._state = ResourceLoaderState.READY; + return bufferLoader; + } catch (error) { + if (bufferLoader.isDestroyed()) { + return; + } + + bufferLoader._state = ResourceLoaderState.FAILED; + const errorMessage = `Failed to load external buffer: ${resource.url}`; + throw bufferLoader.getError(errorMessage, error); + } } /** diff --git a/packages/engine/Source/Scene/Cesium3DTileContentFactory.js b/packages/engine/Source/Scene/Cesium3DTileContentFactory.js index 9c8dd3e6230f..23c6b39e8928 100644 --- a/packages/engine/Source/Scene/Cesium3DTileContentFactory.js +++ b/packages/engine/Source/Scene/Cesium3DTileContentFactory.js @@ -92,9 +92,11 @@ const Cesium3DTileContentFactory = { const dataView = new DataView(arrayBuffer, byteOffset); const byteLength = dataView.getUint32(8, true); const glb = new Uint8Array(arrayBuffer, byteOffset, byteLength); + // This should be replace with fromGltfAsync when readyPromise is deprecated across 3D Tiles functions return Model3DTileContent.fromGltf(tileset, tile, resource, glb); }, gltf: function (tileset, tile, resource, json) { + // This should be replace with fromGltfAsync when readyPromise is deprecated across 3D Tiles functions return Model3DTileContent.fromGltf(tileset, tile, resource, json); }, geoJson: function (tileset, tile, resource, json) { diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index 90477276db8d..348462c03d36 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -106,7 +106,8 @@ function Cesium3DTilesVoxelProvider(options) { .then(function (tileset) { tilesetJson = tileset; validate(tilesetJson); - return getMetadataSchemaLoader(tilesetJson, resource).promise; + const schemaLoader = getMetadataSchemaLoader(tilesetJson, resource); + return schemaLoader.load(); }) .then(function (schemaLoader) { const root = tilesetJson.root; @@ -306,9 +307,9 @@ function getCylinderShape(cylinder, tileTransform) { function getMetadataSchemaLoader(tilesetJson, resource) { const { schemaUri, schema } = tilesetJson; if (!defined(schemaUri)) { - return ResourceCache.loadSchema({ schema }); + return ResourceCache.getSchemaLoader({ schema }); } - return ResourceCache.loadSchema({ + return ResourceCache.getSchemaLoader({ resource: resource.getDerivedResource({ url: schemaUri, }), diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index d3f8209d05c8..977464a25062 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -2151,11 +2151,11 @@ function processMetadataExtension(tileset, tilesetJson) { const resource = tileset._resource.getDerivedResource({ url: metadataJson.schemaUri, }); - schemaLoader = ResourceCache.loadSchema({ + schemaLoader = ResourceCache.getSchemaLoader({ resource: resource, }); } else if (defined(metadataJson.schema)) { - schemaLoader = ResourceCache.loadSchema({ + schemaLoader = ResourceCache.getSchemaLoader({ schema: metadataJson.schema, }); } else { @@ -2164,7 +2164,7 @@ function processMetadataExtension(tileset, tilesetJson) { tileset._schemaLoader = schemaLoader; - return schemaLoader.promise.then(function (schemaLoader) { + return schemaLoader.load().then(function (schemaLoader) { tileset._metadataExtension = new Cesium3DTilesetMetadata({ schema: schemaLoader.schema, metadataJson: metadataJson, diff --git a/packages/engine/Source/Scene/GltfBufferViewLoader.js b/packages/engine/Source/Scene/GltfBufferViewLoader.js index e43b2ace1b8b..717ba0b09fab 100644 --- a/packages/engine/Source/Scene/GltfBufferViewLoader.js +++ b/packages/engine/Source/Scene/GltfBufferViewLoader.js @@ -87,7 +87,6 @@ function GltfBufferViewLoader(options) { this._typedArray = undefined; this._state = ResourceLoaderState.UNLOADED; this._promise = undefined; - this._process = function (loader, frameState) {}; } if (defined(Object.create)) { @@ -96,20 +95,6 @@ if (defined(Object.create)) { } Object.defineProperties(GltfBufferViewLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. - * - * @memberof GltfBufferViewLoader.prototype - * - * @type {Promise.|undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -145,77 +130,61 @@ Object.defineProperties(GltfBufferViewLoader.prototype, { * @returns {Promise.} A promise which resolves to the loader when the resource loading is completed. * @private */ -GltfBufferViewLoader.prototype.load = function () { - const bufferLoader = getBufferLoader(this); - this._bufferLoader = bufferLoader; - this._state = ResourceLoaderState.LOADING; - - const that = this; - const bufferViewPromise = new Promise(function (resolve) { - that._process = function (loader, frameState) { - if (!loader._hasMeshopt) { - return; - } +GltfBufferViewLoader.prototype.load = async function () { + if (defined(this._promise)) { + return this._promise; + } - if (!defined(loader._typedArray)) { - return; - } + this._promise = (async () => { + this._state = ResourceLoaderState.LOADING; + try { + const bufferLoader = getBufferLoader(this); + this._bufferLoader = bufferLoader; + await bufferLoader.load(); - if (loader._state !== ResourceLoaderState.PROCESSING) { + if (this.isDestroyed()) { return; } - const count = loader._meshoptCount; - const byteStride = loader._meshoptByteStride; - const result = new Uint8Array(count * byteStride); - MeshoptDecoder.decodeGltfBuffer( - result, - count, - byteStride, - loader._typedArray, - loader._meshoptMode, - loader._meshoptFilter - ); - - loader._typedArray = result; - loader._state = ResourceLoaderState.READY; - resolve(loader); - }; - }); - - this._promise = bufferLoader.promise - .then(function () { - if (that.isDestroyed()) { - return; - } const bufferTypedArray = bufferLoader.typedArray; const bufferViewTypedArray = new Uint8Array( bufferTypedArray.buffer, - bufferTypedArray.byteOffset + that._byteOffset, - that._byteLength + bufferTypedArray.byteOffset + this._byteOffset, + this._byteLength ); // Unload the buffer - that.unload(); - - that._typedArray = bufferViewTypedArray; - if (that._hasMeshopt) { - that._state = ResourceLoaderState.PROCESSING; - return bufferViewPromise; + this.unload(); + + this._typedArray = bufferViewTypedArray; + if (this._hasMeshopt) { + const count = this._meshoptCount; + const byteStride = this._meshoptByteStride; + const result = new Uint8Array(count * byteStride); + MeshoptDecoder.decodeGltfBuffer( + result, + count, + byteStride, + this._typedArray, + this._meshoptMode, + this._meshoptFilter + ); + this._typedArray = result; } - that._state = ResourceLoaderState.READY; - return that; - }) - .catch(function (error) { - if (that.isDestroyed()) { + this._state = ResourceLoaderState.READY; + return this; + } catch (error) { + if (this.isDestroyed()) { return; } - that.unload(); - that._state = ResourceLoaderState.FAILED; + + this.unload(); + this._state = ResourceLoaderState.FAILED; const errorMessage = "Failed to load buffer view"; - return Promise.reject(that.getError(errorMessage, error)); - }); + throw this.getError(errorMessage, error); + } + })(); return this._promise; }; @@ -228,30 +197,16 @@ function getBufferLoader(bufferViewLoader) { const resource = baseResource.getDerivedResource({ url: buffer.uri, }); - return resourceCache.loadExternalBuffer({ + return resourceCache.getExternalBufferLoader({ resource: resource, }); } - return resourceCache.loadEmbeddedBuffer({ + return resourceCache.getEmbeddedBufferLoader({ parentResource: bufferViewLoader._gltfResource, bufferId: bufferViewLoader._bufferId, }); } -/** - * Processes the resources. For a BufferView that does not have the EXT_meshopt_compression extension, there - * is no processing that needs to happen, so this function returns immediately. - * - * @param {FrameState} frameState The frame state. - */ -GltfBufferViewLoader.prototype.process = function (frameState) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("frameState", frameState); - //>>includeEnd('debug'); - - return this._process(this, frameState); -}; - /** * Unloads the resource. * @private diff --git a/packages/engine/Source/Scene/GltfDracoLoader.js b/packages/engine/Source/Scene/GltfDracoLoader.js index 49481b13af00..038e73d5c0d4 100644 --- a/packages/engine/Source/Scene/GltfDracoLoader.js +++ b/packages/engine/Source/Scene/GltfDracoLoader.js @@ -54,7 +54,7 @@ function GltfDracoLoader(options) { this._decodedData = undefined; this._state = ResourceLoaderState.UNLOADED; this._promise = undefined; - this._process = function (loader, frameState) {}; + this._dracoError = undefined; } if (defined(Object.create)) { @@ -63,20 +63,6 @@ if (defined(Object.create)) { } Object.defineProperties(GltfDracoLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready. - * - * @memberof GltfDracoLoader.prototype - * - * @type {Promise.} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -112,101 +98,39 @@ Object.defineProperties(GltfDracoLoader.prototype, { * @returns {Promise.} A promise which resolves to the loader when the resource loading is completed. * @private */ -GltfDracoLoader.prototype.load = function () { - const resourceCache = this._resourceCache; - const bufferViewLoader = resourceCache.loadBufferView({ - gltf: this._gltf, - bufferViewId: this._draco.bufferView, - gltfResource: this._gltfResource, - baseResource: this._baseResource, - }); - - this._bufferViewLoader = bufferViewLoader; +GltfDracoLoader.prototype.load = async function () { + if (defined(this._promise)) { + return this._promise; + } + this._state = ResourceLoaderState.LOADING; - const that = this; - const dracoPromise = new Promise(function (resolve, reject) { - that._process = function (loader, frameState) { - if (!defined(loader._bufferViewTypedArray)) { - // Not ready to decode the Draco buffer + const resourceCache = this._resourceCache; + this._promise = (async () => { + try { + const bufferViewLoader = resourceCache.getBufferViewLoader({ + gltf: this._gltf, + bufferViewId: this._draco.bufferView, + gltfResource: this._gltfResource, + baseResource: this._baseResource, + }); + this._bufferViewLoader = bufferViewLoader; + await bufferViewLoader.load(); + + if (this.isDestroyed()) { return; } - if (defined(loader._decodePromise)) { - // Currently decoding + this._bufferViewTypedArray = bufferViewLoader.typedArray; + this._state = ResourceLoaderState.PROCESSING; + return this; + } catch (error) { + if (this.isDestroyed()) { return; } - const draco = loader._draco; - const gltf = loader._gltf; - const bufferViews = gltf.bufferViews; - const bufferViewId = draco.bufferView; - const bufferView = bufferViews[bufferViewId]; - const compressedAttributes = draco.attributes; - - const decodeOptions = { - // Need to make a copy of the typed array otherwise the underlying - // ArrayBuffer may be accessed on both the worker and the main thread. This - // leads to errors such as "ArrayBuffer at index 0 is already detached". - // PERFORMANCE_IDEA: Look into SharedArrayBuffer to get around this. - array: new Uint8Array(loader._bufferViewTypedArray), - bufferView: bufferView, - compressedAttributes: compressedAttributes, - dequantizeInShader: true, - }; - - const decodePromise = DracoLoader.decodeBufferView(decodeOptions); - - if (!defined(decodePromise)) { - // Cannot schedule task this frame - return; - } - - loader._decodePromise = decodePromise - .then(function (results) { - if (loader.isDestroyed()) { - resolve(); - return; - } - - // Unload everything except the decoded data - loader.unload(); - - loader._decodedData = { - indices: results.indexArray, - vertexAttributes: results.attributeData, - }; - loader._state = ResourceLoaderState.READY; - resolve(loader); - }) - .catch(function (e) { - if (loader.isDestroyed()) { - resolve(); - return; - } - - reject(e); - }); - }; - }); - - this._promise = bufferViewLoader.promise - .then(function () { - if (that.isDestroyed()) { - return; - } - // Now wait for process() to run to finish loading - that._bufferViewTypedArray = bufferViewLoader.typedArray; - that._state = ResourceLoaderState.PROCESSING; - - return dracoPromise; - }) - .catch(function (error) { - if (that.isDestroyed()) { - return; - } - - return handleError(that, error); - }); + handleError(this, error); + } + })(); return this._promise; }; @@ -215,7 +139,7 @@ function handleError(dracoLoader, error) { dracoLoader.unload(); dracoLoader._state = ResourceLoaderState.FAILED; const errorMessage = "Failed to load Draco"; - return Promise.reject(dracoLoader.getError(errorMessage, error)); + throw dracoLoader.getError(errorMessage, error); } /** @@ -229,7 +153,78 @@ GltfDracoLoader.prototype.process = function (frameState) { Check.typeOf.object("frameState", frameState); //>>includeEnd('debug'); - return this._process(this, frameState); + if (this._state === ResourceLoaderState.READY) { + return true; + } + + if (this._state !== ResourceLoaderState.PROCESSING) { + return false; + } + + if (defined(this._dracoError)) { + handleError(this, this._dracoError); + } + + if (!defined(this._bufferViewTypedArray)) { + // Not ready to decode the Draco buffer + return false; + } + + if (defined(this._decodePromise)) { + // Currently decoding + return false; + } + + const draco = this._draco; + const gltf = this._gltf; + const bufferViews = gltf.bufferViews; + const bufferViewId = draco.bufferView; + const bufferView = bufferViews[bufferViewId]; + const compressedAttributes = draco.attributes; + + const decodeOptions = { + // Need to make a copy of the typed array otherwise the underlying + // ArrayBuffer may be accessed on both the worker and the main thread. This + // leads to errors such as "ArrayBuffer at index 0 is already detached". + // PERFORMANCE_IDEA: Look into SharedArrayBuffer to get around this. + array: new Uint8Array(this._bufferViewTypedArray), + bufferView: bufferView, + compressedAttributes: compressedAttributes, + dequantizeInShader: true, + }; + + const decodePromise = DracoLoader.decodeBufferView(decodeOptions); + + if (!defined(decodePromise)) { + // Cannot schedule task this frame + return false; + } + + this._decodePromise = (async () => { + try { + const results = await decodePromise; + if (this.isDestroyed()) { + return; + } + + // Unload everything except the decoded data + this.unload(); + + this._decodedData = { + indices: results.indexArray, + vertexAttributes: results.attributeData, + }; + this._state = ResourceLoaderState.READY; + return this._baseResource; + } catch (error) { + if (this.isDestroyed()) { + return; + } + + // Capture this error so it can be thrown on the next `process` call + this._dracoError = error; + } + })(); }; /** diff --git a/packages/engine/Source/Scene/GltfImageLoader.js b/packages/engine/Source/Scene/GltfImageLoader.js index 53e42898325b..5994ed9e94e0 100644 --- a/packages/engine/Source/Scene/GltfImageLoader.js +++ b/packages/engine/Source/Scene/GltfImageLoader.js @@ -68,20 +68,6 @@ if (defined(Object.create)) { } Object.defineProperties(GltfImageLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. - * - * @memberof GltfImageLoader.prototype - * - * @type {Promise.|undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -132,6 +118,10 @@ Object.defineProperties(GltfImageLoader.prototype, { * @private */ GltfImageLoader.prototype.load = function () { + if (defined(this._promise)) { + return this._promise; + } + if (defined(this._bufferViewId)) { this._promise = loadFromBufferView(this); return this._promise; @@ -158,78 +148,78 @@ function getImageAndMipLevels(image) { }; } -function loadFromBufferView(imageLoader) { +async function loadFromBufferView(imageLoader) { + imageLoader._state = ResourceLoaderState.LOADING; const resourceCache = imageLoader._resourceCache; - const bufferViewLoader = resourceCache.loadBufferView({ - gltf: imageLoader._gltf, - bufferViewId: imageLoader._bufferViewId, - gltfResource: imageLoader._gltfResource, - baseResource: imageLoader._baseResource, - }); + try { + const bufferViewLoader = resourceCache.getBufferViewLoader({ + gltf: imageLoader._gltf, + bufferViewId: imageLoader._bufferViewId, + gltfResource: imageLoader._gltfResource, + baseResource: imageLoader._baseResource, + }); + imageLoader._bufferViewLoader = bufferViewLoader; + await bufferViewLoader.load(); - imageLoader._bufferViewLoader = bufferViewLoader; - imageLoader._state = ResourceLoaderState.LOADING; + if (imageLoader.isDestroyed()) { + return; + } - return bufferViewLoader.promise - .then(function () { - if (imageLoader.isDestroyed()) { - return; - } - - const typedArray = bufferViewLoader.typedArray; - return loadImageFromBufferTypedArray(typedArray).then(function (image) { - if (imageLoader.isDestroyed()) { - return; - } - - const imageAndMipLevels = getImageAndMipLevels(image); - - // Unload everything except the image - imageLoader.unload(); - - imageLoader._image = imageAndMipLevels.image; - imageLoader._mipLevels = imageAndMipLevels.mipLevels; - imageLoader._state = ResourceLoaderState.READY; - return imageLoader; - }); - }) - .catch(function (error) { - if (imageLoader.isDestroyed()) { - return; - } - return handleError(imageLoader, error, "Failed to load embedded image"); - }); + const typedArray = bufferViewLoader.typedArray; + const image = await loadImageFromBufferTypedArray(typedArray); + if (imageLoader.isDestroyed()) { + return; + } + + const imageAndMipLevels = getImageAndMipLevels(image); + + // Unload everything except the image + imageLoader.unload(); + + imageLoader._image = imageAndMipLevels.image; + imageLoader._mipLevels = imageAndMipLevels.mipLevels; + imageLoader._state = ResourceLoaderState.READY; + + return imageLoader; + } catch (error) { + if (imageLoader.isDestroyed()) { + return; + } + + return handleError(imageLoader, error, "Failed to load embedded image"); + } } -function loadFromUri(imageLoader) { +async function loadFromUri(imageLoader) { + imageLoader._state = ResourceLoaderState.LOADING; const baseResource = imageLoader._baseResource; const uri = imageLoader._uri; const resource = baseResource.getDerivedResource({ url: uri, }); - imageLoader._state = ResourceLoaderState.LOADING; - return loadImageFromUri(resource) - .then(function (image) { - if (imageLoader.isDestroyed()) { - return; - } - - const imageAndMipLevels = getImageAndMipLevels(image); - - // Unload everything except the image - imageLoader.unload(); - - imageLoader._image = imageAndMipLevels.image; - imageLoader._mipLevels = imageAndMipLevels.mipLevels; - imageLoader._state = ResourceLoaderState.READY; - return imageLoader; - }) - .catch(function (error) { - if (imageLoader.isDestroyed()) { - return; - } - return handleError(imageLoader, error, `Failed to load image: ${uri}`); - }); + + try { + const image = await loadImageFromUri(resource); + if (imageLoader.isDestroyed()) { + return; + } + + const imageAndMipLevels = getImageAndMipLevels(image); + + // Unload everything except the image + imageLoader.unload(); + + imageLoader._image = imageAndMipLevels.image; + imageLoader._mipLevels = imageAndMipLevels.mipLevels; + imageLoader._state = ResourceLoaderState.READY; + + return imageLoader; + } catch (error) { + if (imageLoader.isDestroyed()) { + return; + } + return handleError(imageLoader, error, `Failed to load image: ${uri}`); + } } function handleError(imageLoader, error, errorMessage) { @@ -269,7 +259,7 @@ function getMimeTypeFromTypedArray(typedArray) { throw new RuntimeError("Image format is not recognized"); } -function loadImageFromBufferTypedArray(typedArray) { +async function loadImageFromBufferTypedArray(typedArray) { const mimeType = getMimeTypeFromTypedArray(typedArray); if (mimeType === "image/ktx2") { // Need to make a copy of the embedded KTX2 buffer otherwise the underlying diff --git a/packages/engine/Source/Scene/GltfIndexBufferLoader.js b/packages/engine/Source/Scene/GltfIndexBufferLoader.js index b5006e4cbef2..dc7bdcf9b6d1 100644 --- a/packages/engine/Source/Scene/GltfIndexBufferLoader.js +++ b/packages/engine/Source/Scene/GltfIndexBufferLoader.js @@ -79,7 +79,6 @@ function GltfIndexBufferLoader(options) { this._buffer = undefined; this._state = ResourceLoaderState.UNLOADED; this._promise = undefined; - this._process = function (loader, frameState) {}; } if (defined(Object.create)) { @@ -88,20 +87,6 @@ if (defined(Object.create)) { } Object.defineProperties(GltfIndexBufferLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource has not yet started loading. - * - * @memberof GltfIndexBufferLoader.prototype - * - * @type {Promise.|undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -168,135 +153,72 @@ const scratchIndexBufferJob = new CreateIndexBufferJob(); * @returns {Promise.} A promise which resolves to the loader when the resource loading is completed. * @private */ -GltfIndexBufferLoader.prototype.load = function () { - let promise; +GltfIndexBufferLoader.prototype.load = async function () { + if (defined(this._promise)) { + return this._promise; + } if (defined(this._draco)) { - promise = loadFromDraco(this); - } else { - promise = loadFromBufferView(this); + this._promise = loadFromDraco(this); + return this._promise; } - const that = this; - const processPromise = new Promise(function (resolve) { - that._process = function (loader, frameState) { - if (loader._state === ResourceLoaderState.READY) { - return; - } - - const typedArray = loader._typedArray; - const indexDatatype = loader._indexDatatype; - - if (defined(loader._dracoLoader)) { - loader._dracoLoader.process(frameState); - } - - if (defined(loader._bufferViewLoader)) { - loader._bufferViewLoader.process(frameState); - } - - if (!defined(typedArray)) { - // Buffer view hasn't been loaded yet - return; - } - - let buffer; - if (loader._loadBuffer && loader._asynchronous) { - const indexBufferJob = scratchIndexBufferJob; - indexBufferJob.set(typedArray, indexDatatype, frameState.context); - const jobScheduler = frameState.jobScheduler; - if (!jobScheduler.execute(indexBufferJob, JobType.BUFFER)) { - // Job scheduler is full. Try again next frame. - return; - } - buffer = indexBufferJob.buffer; - } else if (loader._loadBuffer) { - buffer = createIndexBuffer( - typedArray, - indexDatatype, - frameState.context - ); - } - - // Unload everything except the index buffer and/or typed array. - loader.unload(); - - loader._buffer = buffer; - loader._typedArray = loader._loadTypedArray ? typedArray : undefined; - loader._state = ResourceLoaderState.READY; - resolve(loader); - }; - }); - - this._promise = promise - .then(function () { - if (that.isDestroyed()) { - return; - } - - return processPromise; - }) - .catch(function (error) { - if (that.isDestroyed()) { - return; - } - - return handleError(that, error); - }); - + this._promise = loadFromBufferView(this); return this._promise; }; -function loadFromDraco(indexBufferLoader) { +async function loadFromDraco(indexBufferLoader) { + indexBufferLoader._state = ResourceLoaderState.LOADING; const resourceCache = indexBufferLoader._resourceCache; - const dracoLoader = resourceCache.loadDraco({ - gltf: indexBufferLoader._gltf, - draco: indexBufferLoader._draco, - gltfResource: indexBufferLoader._gltfResource, - baseResource: indexBufferLoader._baseResource, - }); - indexBufferLoader._dracoLoader = dracoLoader; - indexBufferLoader._state = ResourceLoaderState.LOADING; + try { + const dracoLoader = resourceCache.getDracoLoader({ + gltf: indexBufferLoader._gltf, + draco: indexBufferLoader._draco, + gltfResource: indexBufferLoader._gltfResource, + baseResource: indexBufferLoader._baseResource, + }); + indexBufferLoader._dracoLoader = dracoLoader; + await dracoLoader.load(); - return dracoLoader.promise.then(function () { if (indexBufferLoader.isDestroyed()) { return; } + // Now wait for process() to run to finish loading - const typedArray = dracoLoader.decodedData.indices.typedArray; - indexBufferLoader._typedArray = typedArray; - // The index datatype may be a smaller datatype after draco decode - indexBufferLoader._indexDatatype = ComponentDatatype.fromTypedArray( - typedArray - ); - indexBufferLoader._state = ResourceLoaderState.PROCESSING; + indexBufferLoader._state = ResourceLoaderState.LOADED; return indexBufferLoader; - }); + } catch (error) { + if (indexBufferLoader.isDestroyed()) { + return; + } + + handleError(indexBufferLoader, error); + } } -function loadFromBufferView(indexBufferLoader) { +async function loadFromBufferView(indexBufferLoader) { const gltf = indexBufferLoader._gltf; const accessorId = indexBufferLoader._accessorId; const accessor = gltf.accessors[accessorId]; const bufferViewId = accessor.bufferView; - const resourceCache = indexBufferLoader._resourceCache; - const bufferViewLoader = resourceCache.loadBufferView({ - gltf: gltf, - bufferViewId: bufferViewId, - gltfResource: indexBufferLoader._gltfResource, - baseResource: indexBufferLoader._baseResource, - }); indexBufferLoader._state = ResourceLoaderState.LOADING; - indexBufferLoader._bufferViewLoader = bufferViewLoader; + const resourceCache = indexBufferLoader._resourceCache; + try { + const bufferViewLoader = resourceCache.getBufferViewLoader({ + gltf: gltf, + bufferViewId: bufferViewId, + gltfResource: indexBufferLoader._gltfResource, + baseResource: indexBufferLoader._baseResource, + }); + indexBufferLoader._bufferViewLoader = bufferViewLoader; - return bufferViewLoader.promise.then(function () { + await bufferViewLoader.load(); if (indexBufferLoader.isDestroyed()) { return; } - // Now wait for process() to run to finish loading const bufferViewTypedArray = bufferViewLoader.typedArray; indexBufferLoader._typedArray = createIndicesTypedArray( indexBufferLoader, @@ -304,7 +226,13 @@ function loadFromBufferView(indexBufferLoader) { ); indexBufferLoader._state = ResourceLoaderState.PROCESSING; return indexBufferLoader; - }); + } catch (error) { + if (indexBufferLoader.isDestroyed()) { + return; + } + + handleError(indexBufferLoader, error); + } } function createIndicesTypedArray(indexBufferLoader, bufferViewTypedArray) { @@ -346,8 +274,7 @@ function handleError(indexBufferLoader, error) { indexBufferLoader.unload(); indexBufferLoader._state = ResourceLoaderState.FAILED; const errorMessage = "Failed to load index buffer"; - error = indexBufferLoader.getError(errorMessage, error); - return Promise.reject(error); + throw indexBufferLoader.getError(errorMessage, error); } function CreateIndexBufferJob() { @@ -397,7 +324,63 @@ GltfIndexBufferLoader.prototype.process = function (frameState) { Check.typeOf.object("frameState", frameState); //>>includeEnd('debug'); - return this._process(this, frameState); + if (this._state === ResourceLoaderState.READY) { + return true; + } + + if ( + this._state !== ResourceLoaderState.LOADED && + this._state !== ResourceLoaderState.PROCESSING + ) { + return false; + } + + const typedArray = this._typedArray; + const indexDatatype = this._indexDatatype; + + if (defined(this._dracoLoader)) { + try { + const ready = this._dracoLoader.process(frameState); + if (ready) { + const dracoLoader = this._dracoLoader; + const typedArray = dracoLoader.decodedData.indices.typedArray; + this._typedArray = typedArray; + // The index datatype may be a smaller datatype after draco decode + this._indexDatatype = ComponentDatatype.fromTypedArray(typedArray); + } + } catch (error) { + handleError(this, error); + } + } + + if (!defined(typedArray)) { + // Buffer view hasn't been loaded yet + return false; + } + + let buffer; + if (this._loadBuffer && this._asynchronous) { + const indexBufferJob = scratchIndexBufferJob; + indexBufferJob.set(typedArray, indexDatatype, frameState.context); + const jobScheduler = frameState.jobScheduler; + if (!jobScheduler.execute(indexBufferJob, JobType.BUFFER)) { + // Job scheduler is full. Try again next frame. + return false; + } + buffer = indexBufferJob.buffer; + } else if (this._loadBuffer) { + buffer = createIndexBuffer(typedArray, indexDatatype, frameState.context); + } + + // Unload everything except the index buffer and/or typed array. + this.unload(); + + this._buffer = buffer; + this._typedArray = this._loadTypedArray ? typedArray : undefined; + this._state = ResourceLoaderState.READY; + + this._resourceCache.statistics.addGeometryLoader(this); + return true; }; /** diff --git a/packages/engine/Source/Scene/GltfJsonLoader.js b/packages/engine/Source/Scene/GltfJsonLoader.js index fdf7acb60efd..c61017ba0874 100644 --- a/packages/engine/Source/Scene/GltfJsonLoader.js +++ b/packages/engine/Source/Scene/GltfJsonLoader.js @@ -68,20 +68,6 @@ if (defined(Object.create)) { } Object.defineProperties(GltfJsonLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. - * - * @memberof GltfJsonLoader.prototype - * - * @type {Promise.|undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -117,56 +103,55 @@ Object.defineProperties(GltfJsonLoader.prototype, { * @returns {Promise.} A promise which resolves to the loader when the resource loading is completed. * @private */ -GltfJsonLoader.prototype.load = function () { +GltfJsonLoader.prototype.load = async function () { + if (defined(this._promise)) { + return this._promise; + } + this._state = ResourceLoaderState.LOADING; - let processPromise; if (defined(this._gltfJson)) { - processPromise = processGltfJson(this, this._gltfJson); - } else if (defined(this._typedArray)) { - processPromise = processGltfTypedArray(this, this._typedArray); - } else { - processPromise = loadFromUri(this); + this._promise = processGltfJson(this, this._gltfJson); + return this._promise; } - const that = this; - this._promise = processPromise - .then(function (gltf) { - if (that.isDestroyed()) { - return; - } - that._gltf = gltf; - that._state = ResourceLoaderState.READY; - return that; - }) - .catch(function (error) { - if (that.isDestroyed()) { - return; - } - return handleError(that, error); - }); + if (defined(this._typedArray)) { + this._promise = processGltfTypedArray(this, this._typedArray); + return this._promise; + } + this._promise = loadFromUri(this); return this._promise; }; -function loadFromUri(gltfJsonLoader) { - return gltfJsonLoader._fetchGltf().then(function (arrayBuffer) { +async function loadFromUri(gltfJsonLoader) { + let typedArray; + try { + const arrayBuffer = await gltfJsonLoader._fetchGltf(); if (gltfJsonLoader.isDestroyed()) { return; } - const typedArray = new Uint8Array(arrayBuffer); - return processGltfTypedArray(gltfJsonLoader, typedArray); - }); + + typedArray = new Uint8Array(arrayBuffer); + } catch (error) { + if (gltfJsonLoader.isDestroyed()) { + return; + } + + handleError(gltfJsonLoader, error); + } + + return processGltfTypedArray(gltfJsonLoader, typedArray); } function handleError(gltfJsonLoader, error) { gltfJsonLoader.unload(); gltfJsonLoader._state = ResourceLoaderState.FAILED; const errorMessage = `Failed to load glTF: ${gltfJsonLoader._gltfResource.url}`; - return Promise.reject(gltfJsonLoader.getError(errorMessage, error)); + throw gltfJsonLoader.getError(errorMessage, error); } -function upgradeVersion(gltfJsonLoader, gltf) { +async function upgradeVersion(gltfJsonLoader, gltf) { if ( defined(gltf.asset) && gltf.asset.version === "2.0" && @@ -188,23 +173,25 @@ function upgradeVersion(gltfJsonLoader, gltf) { url: buffer.uri, }); const resourceCache = gltfJsonLoader._resourceCache; - const bufferLoader = resourceCache.loadExternalBuffer({ + const bufferLoader = resourceCache.getExternalBufferLoader({ resource: resource, }); - gltfJsonLoader._bufferLoaders.push(bufferLoader); promises.push( - bufferLoader.promise.then(function (bufferLoader) { + bufferLoader.load().then(function () { + if (bufferLoader.isDestroyed()) { + return; + } + buffer.extras._pipeline.source = bufferLoader.typedArray; }) ); } }); - return Promise.all(promises).then(function () { - updateVersion(gltf); - }); + await Promise.all(promises); + updateVersion(gltf); } function decodeDataUris(gltf) { @@ -233,42 +220,54 @@ function loadEmbeddedBuffers(gltfJsonLoader, gltf) { const source = buffer.extras._pipeline.source; if (defined(source) && !defined(buffer.uri)) { const resourceCache = gltfJsonLoader._resourceCache; - const bufferLoader = resourceCache.loadEmbeddedBuffer({ + const bufferLoader = resourceCache.getEmbeddedBufferLoader({ parentResource: gltfJsonLoader._gltfResource, bufferId: bufferId, typedArray: source, }); - gltfJsonLoader._bufferLoaders.push(bufferLoader); - promises.push(bufferLoader.promise); + promises.push(bufferLoader.load()); } }); return Promise.all(promises); } -function processGltfJson(gltfJsonLoader, gltf) { - addPipelineExtras(gltf); - - return decodeDataUris(gltf) - .then(function () { - return upgradeVersion(gltfJsonLoader, gltf); - }) - .then(function () { - addDefaults(gltf); - return loadEmbeddedBuffers(gltfJsonLoader, gltf); - }) - .then(function () { - removePipelineExtras(gltf); - return gltf; - }); +async function processGltfJson(gltfJsonLoader, gltf) { + try { + addPipelineExtras(gltf); + + await decodeDataUris(gltf); + await upgradeVersion(gltfJsonLoader, gltf); + addDefaults(gltf); + await loadEmbeddedBuffers(gltfJsonLoader, gltf); + removePipelineExtras(gltf); + + gltfJsonLoader._gltf = gltf; + gltfJsonLoader._state = ResourceLoaderState.READY; + return gltfJsonLoader; + } catch (error) { + if (gltfJsonLoader.isDestroyed()) { + return; + } + + handleError(gltfJsonLoader, error); + } } -function processGltfTypedArray(gltfJsonLoader, typedArray) { +async function processGltfTypedArray(gltfJsonLoader, typedArray) { let gltf; - if (getMagic(typedArray) === "glTF") { - gltf = parseGlb(typedArray); - } else { - gltf = getJsonFromTypedArray(typedArray); + try { + if (getMagic(typedArray) === "glTF") { + gltf = parseGlb(typedArray); + } else { + gltf = getJsonFromTypedArray(typedArray); + } + } catch (error) { + if (gltfJsonLoader.isDestroyed()) { + return; + } + + handleError(gltfJsonLoader, error); } return processGltfJson(gltfJsonLoader, gltf); diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index 20655d764f42..6b2b5ca7f41e 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -242,9 +242,8 @@ function GltfLoader(options) { this._state = GltfLoaderState.NOT_LOADED; this._textureState = GltfLoaderState.NOT_LOADED; this._promise = undefined; - this._texturesLoadedPromise = undefined; - this._process = function (loader, frameState) {}; - this._processTextures = function (loader, frameState) {}; + this._processError = undefined; + this._textureErrors = []; // Information about whether to load primitives as typed arrays or buffers, // and whether post-processing is needed after loading (e.g. for @@ -255,9 +254,12 @@ function GltfLoader(options) { this._loaderPromises = []; this._textureLoaders = []; this._texturesPromises = []; + this._textureCallbacks = {}; this._bufferViewLoaders = []; this._geometryLoaders = []; + this._geometryCallbacks = []; this._structuralMetadataLoader = undefined; + this._loadResourcesPromise = undefined; // In some cases where geometry post-processing is needed (like generating // outlines) new attributes are added that may have GPU resources attached. @@ -275,231 +277,207 @@ if (defined(Object.create)) { Object.defineProperties(GltfLoader.prototype, { /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. + * The cache key of the resource. * * @memberof GltfLoader.prototype * - * @type {Promise.|undefined} + * @type {String} * @readonly * @private */ - promise: { + cacheKey: { get: function () { - return this._promise; + return undefined; }, }, /** - * The cache key of the resource. + * The loaded components. * * @memberof GltfLoader.prototype * - * @type {String} + * @type {ModelComponents.Components} * @readonly * @private */ - cacheKey: { + components: { get: function () { - return undefined; + return this._components; }, }, /** - * The loaded components. + * The loaded glTF json. * * @memberof GltfLoader.prototype * - * @type {ModelComponents.Components} + * @type {object} * @readonly * @private */ - components: { + gltfJson: { get: function () { - return this._components; + if (defined(this._gltfJsonLoader)) { + return this._gltfJsonLoader.gltf; + } + return this._gltfJson; }, }, - /** - * A promise that resolves when all textures are loaded. - * When incrementallyLoadTextures is true this may resolve after - * promise resolves. + * true if textures are loaded separately from the other glTF resources. * * @memberof GltfLoader.prototype * - * @type {Promise} + * @type {boolean} * @readonly * @private */ - texturesLoadedPromise: { + incrementallyLoadTextures: { get: function () { - return this._texturesLoadedPromise; + return this._incrementallyLoadTextures; }, }, }); /** - * Loads the resource. - * @returns {Promise.} A promise which resolves to the loader when the resource loading is completed. - * @private + * Loads the gltf object */ -GltfLoader.prototype.load = function () { - const gltfJsonLoader = ResourceCache.loadGltfJson({ - gltfResource: this._gltfResource, - baseResource: this._baseResource, - typedArray: this._typedArray, - gltfJson: this._gltfJson, - }); - - this._gltfJsonLoader = gltfJsonLoader; - this._state = GltfLoaderState.LOADING; - this._textureState = GltfLoaderState.LOADING; +async function loadGltfJson(loader) { + loader._state = GltfLoaderState.LOADING; + loader._textureState = GltfLoaderState.LOADING; + + try { + const gltfJsonLoader = ResourceCache.getGltfJsonLoader({ + gltfResource: loader._gltfResource, + baseResource: loader._baseResource, + typedArray: loader._typedArray, + gltfJson: loader._gltfJson, + }); + loader._gltfJsonLoader = gltfJsonLoader; + await gltfJsonLoader.load(); - const that = this; - let textureProcessPromise; - const processPromise = new Promise(function (resolve, reject) { - textureProcessPromise = new Promise(function ( - resolveTextures, - rejectTextures + if ( + loader.isDestroyed() || + loader.isUnloaded() || + gltfJsonLoader.isDestroyed() ) { - that._process = function (loader, frameState) { - if (!FeatureDetection.supportsWebP.initialized) { - FeatureDetection.supportsWebP.initialize(); - return; - } - - if (loader._state === GltfLoaderState.LOADED) { - loader._state = GltfLoaderState.PROCESSING; - - const supportedImageFormats = new SupportedImageFormats({ - webp: FeatureDetection.supportsWebP(), - basis: frameState.context.supportsBasis, - }); - - let gltf; - if (defined(loader._gltfJsonLoader)) { - gltf = loader._gltfJsonLoader.gltf; - } else { - gltf = loader._gltfJson; - } - - // Parse the glTF which populates the loaders arrays. The promise will - // resolve once all the loaders are ready (i.e. all external resources - // have been fetched and all GPU resources have been created). Loaders that - // create GPU resources need to be processed every frame until they become - // ready since the JobScheduler is not able to execute all jobs in a single - // frame. Also note that it's fine to call process before a loader is ready - // to process; nothing will happen. - parse( - loader, - gltf, - supportedImageFormats, - frameState, - reject, - rejectTextures - ); - - if (defined(loader._gltfJsonLoader) && loader._releaseGltfJson) { - // Check that the glTF JSON loader is still defined before trying to unload it. - // It may be undefined if the ready promise rejects immediately (which can happen in unit tests) - ResourceCache.unload(loader._gltfJsonLoader); - loader._gltfJsonLoader = undefined; - } - } + return; + } - if (loader._state === GltfLoaderState.PROCESSING) { - processLoaders(loader, frameState); - } + const gltf = gltfJsonLoader.gltf; + const version = gltf.asset.version; + if (version !== "1.0" && version !== "2.0") { + const url = loader._gltfResource.url; + throw new RuntimeError( + `Failed to load ${url}: \nUnsupported glTF version: ${version}` + ); + } - if (loader._state === GltfLoaderState.POST_PROCESSING) { - postProcessGeometry(loader, frameState.context); - loader._state = GltfLoaderState.PROCESSED; - } + const extensionsRequired = gltf.extensionsRequired; + if (defined(extensionsRequired)) { + ModelUtility.checkSupportedExtensions(extensionsRequired); - if (loader._state === GltfLoaderState.PROCESSED) { - // The buffer views can be unloaded once the data is copied. - unloadBufferViews(loader); + // Check for the KHR_mesh_quantization extension here, it will be used later + // in loadAttribute(). + loader._hasKhrMeshQuantization = extensionsRequired.includes( + "KHR_mesh_quantization" + ); + } - // Similarly, if the glTF was loaded from a typed array, release the memory - loader._typedArray = undefined; + loader._state = GltfLoaderState.LOADED; + loader._textureState = GltfLoaderState.LOADED; - loader._state = GltfLoaderState.READY; - resolve(loader); - } - }; + return loader; + } catch (error) { + if (loader.isDestroyed()) { + return; + } - that._processTextures = function (loader, frameState) { - if (loader._textureState === GltfLoaderState.LOADED) { - loader._textureState = GltfLoaderState.PROCESSING; - } + loader._state = GltfLoaderState.FAILED; + loader._textureState = GltfLoaderState.FAILED; + handleError(loader, error); + } +} - if (loader._textureState === GltfLoaderState.PROCESSING) { - let i; - const textureLoaders = loader._textureLoaders; - const textureLoadersLength = textureLoaders.length; - for (i = 0; i < textureLoadersLength; ++i) { - textureLoaders[i].process(frameState); - } - } +async function loadResources(loader, frameState) { + if (!FeatureDetection.supportsWebP.initialized) { + await FeatureDetection.supportsWebP.initialize(); + } - if (loader._textureState === GltfLoaderState.PROCESSED) { - loader._textureState = GltfLoaderState.READY; - resolveTextures(loader); - } - }; - }); + const supportedImageFormats = new SupportedImageFormats({ + webp: FeatureDetection.supportsWebP(), + basis: frameState.context.supportsBasis, }); - this._promise = gltfJsonLoader.promise - .then(function () { - if (that.isDestroyed()) { - return; - } - that._state = GltfLoaderState.LOADED; - that._textureState = GltfLoaderState.LOADED; + // Parse the glTF which populates the loaders arrays. Loading promises will be created, and will + // resolve once the loaders are ready (i.e. all external resources + // have been fetched and all GPU resources have been created). Loaders that + // create GPU resources need to be processed every frame until they become + // ready since the JobScheduler is not able to execute all jobs in a single + // frame. Any promise failures are collected, and will be handled synchronously in process(). Also note that it's fine to call process before a loader is ready + // to process or after it has failed; nothing will happen. + const gltf = loader.gltfJson; + parse(loader, gltf, supportedImageFormats, frameState); - return processPromise; - }) - .catch(function (error) { - if (that.isDestroyed()) { - return; - } - that._state = GltfLoaderState.FAILED; - that._textureState = GltfLoaderState.FAILED; - return handleError(that, error); - }); + // All resource loaders have been created, so we can begin processing + loader._state = GltfLoaderState.PROCESSING; + loader._textureState = GltfLoaderState.PROCESSING; - this._texturesLoadedPromise = textureProcessPromise.catch(function (error) { - if (that.isDestroyed()) { - return; - } + if (defined(loader._gltfJsonLoader) && loader._releaseGltfJson) { + // Check that the glTF JSON loader is still defined before trying to unload it. + // It can be unloaded if the glTF loader is destroyed. + ResourceCache.unload(loader._gltfJsonLoader); + loader._gltfJsonLoader = undefined; + } +} - that._textureState = GltfLoaderState.FAILED; - return handleError(that, error); - }); +/** + * Loads the resource. + * @returns {Promise.} A promise which resolves to the loader when the resource loading is completed. + * @exception {RuntimeError} Unsupported glTF version + * @exception {RuntimeError} Unsupported glTF Extension + * @private + */ +GltfLoader.prototype.load = async function () { + if (defined(this._promise)) { + return this._promise; + } + this._promise = loadGltfJson(this); return this._promise; }; function handleError(gltfLoader, error) { gltfLoader.unload(); const errorMessage = "Failed to load glTF"; - error = gltfLoader.getError(errorMessage, error); - return Promise.reject(error); + throw gltfLoader.getError(errorMessage, error); } function processLoaders(loader, frameState) { - const bufferViewLoaders = loader._bufferViewLoaders; - const bufferViewLoadersLength = bufferViewLoaders.length; - for (let i = 0; i < bufferViewLoadersLength; ++i) { - bufferViewLoaders[i].process(frameState); - } - + let i; + let ready = true; const geometryLoaders = loader._geometryLoaders; const geometryLoadersLength = geometryLoaders.length; - for (let i = 0; i < geometryLoadersLength; ++i) { - geometryLoaders[i].process(frameState); + for (i = 0; i < geometryLoadersLength; ++i) { + const geometryReady = geometryLoaders[i].process(frameState); + if (geometryReady && defined(loader._geometryCallbacks[i])) { + loader._geometryCallbacks[i](); + loader._geometryCallbacks[i] = undefined; + } + ready = ready && geometryReady; } - if (defined(loader._structuralMetadataLoader)) { - loader._structuralMetadataLoader.process(frameState); + const structuralMetadataLoader = loader._structuralMetadataLoader; + if (defined(structuralMetadataLoader)) { + const metadataReady = structuralMetadataLoader.process(frameState); + if (metadataReady) { + loader._components.structuralMetadata = + structuralMetadataLoader.structuralMetadata; + } + ready = ready && metadataReady; + } + + if (ready) { + // Geometry requires further processing + loader._state = GltfLoaderState.POST_PROCESSING; } } @@ -549,6 +527,73 @@ function gatherPostProcessBuffers(loader, primitiveLoadPlan) { } } +/** + * Process loaders other than textures + * @private + */ +GltfLoader.prototype._process = function (frameState) { + if (this._state === GltfLoaderState.READY) { + return true; + } + + if (this._state === GltfLoaderState.PROCESSING) { + processLoaders(this, frameState); + } + + if (this._state === GltfLoaderState.POST_PROCESSING) { + postProcessGeometry(this, frameState.context); + this._state = GltfLoaderState.PROCESSED; + } + + if (this._state === GltfLoaderState.PROCESSED) { + // The buffer views can be unloaded once the data is copied. + unloadBufferViewLoaders(this); + + // Similarly, if the glTF was loaded from a typed array, release the memory + this._typedArray = undefined; + + this._state = GltfLoaderState.READY; + return true; + } + + return false; +}; + +/** + * Process textures other than textures + * @private + */ +GltfLoader.prototype._processTextures = function (frameState) { + if (this._textureState === GltfLoaderState.READY) { + return true; + } + + if (this._textureState !== GltfLoaderState.PROCESSING) { + return false; + } + + let i; + let ready = true; + const textureLoaders = this._textureLoaders; + const textureLoadersLength = textureLoaders.length; + for (i = 0; i < textureLoadersLength; ++i) { + const textureReady = textureLoaders[i].process(frameState); + if (textureReady && defined(this._textureCallbacks[i])) { + this._textureCallbacks[i](); + this._textureCallbacks[i] = undefined; + } + + ready = ready && textureReady; + } + + if (!ready) { + return false; + } + + this._textureState = GltfLoaderState.READY; + return true; +}; + /** * Processes the resource until it becomes ready. * @@ -560,12 +605,62 @@ GltfLoader.prototype.process = function (frameState) { Check.typeOf.object("frameState", frameState); //>>includeEnd('debug'); - this._process(this, frameState); + if ( + this._state === GltfLoaderState.LOADED && + !defined(this._loadResourcesPromise) + ) { + this._loadResourcesPromise = loadResources(this, frameState).catch( + (error) => { + this._processError = error; + } + ); + } + + if (defined(this._processError)) { + this._state = GltfLoaderState.FAILED; + const error = this._processError; + this._processError = undefined; + handleError(this, error); + } + + // Pop the next error of the list in case there are multiple + const textureError = this._textureErrors.pop(); + if (defined(textureError)) { + // There shouldn't be the need to completely unload in this case. Just throw the error. + const error = this.getError("Failed to load glTF texture", textureError); + error.name = "TextureError"; + throw error; + } + + let ready = false; + if (this._state === GltfLoaderState.FAILED) { + return ready; + } + + try { + ready = this._process(frameState); + } catch (error) { + this._state = GltfLoaderState.FAILED; + handleError(this, error); + } + // Since textures can be loaded independently and are handled through a separate promise, they are processed in their own function - this._processTextures(this, frameState); + let texturesReady = false; + try { + texturesReady = this._processTextures(frameState); + } catch (error) { + this._textureState = GltfLoaderState.FAILED; + handleError(this, error); + } + + if (this._incrementallyLoadTextures) { + return ready; + } + + return ready && texturesReady; }; -function loadVertexBuffer( +function getVertexBufferLoader( loader, gltf, accessorId, @@ -578,7 +673,7 @@ function loadVertexBuffer( const accessor = gltf.accessors[accessorId]; const bufferViewId = accessor.bufferView; - const vertexBufferLoader = ResourceCache.loadVertexBuffer({ + const vertexBufferLoader = ResourceCache.getVertexBufferLoader({ gltf: gltf, gltfResource: loader._gltfResource, baseResource: loader._baseResource, @@ -592,12 +687,10 @@ function loadVertexBuffer( loadTypedArray: loadTypedArray, }); - loader._geometryLoaders.push(vertexBufferLoader); - return vertexBufferLoader; } -function loadIndexBuffer( +function getIndexBufferLoader( loader, gltf, accessorId, @@ -606,7 +699,7 @@ function loadIndexBuffer( loadTypedArray, frameState ) { - const indexBufferLoader = ResourceCache.loadIndexBuffer({ + const indexBufferLoader = ResourceCache.getIndexBufferLoader({ gltf: gltf, accessorId: accessorId, gltfResource: loader._gltfResource, @@ -618,13 +711,11 @@ function loadIndexBuffer( loadTypedArray: loadTypedArray, }); - loader._geometryLoaders.push(indexBufferLoader); - return indexBufferLoader; } -function loadBufferView(loader, gltf, bufferViewId) { - const bufferViewLoader = ResourceCache.loadBufferView({ +function getBufferViewLoader(loader, gltf, bufferViewId) { + const bufferViewLoader = ResourceCache.getBufferViewLoader({ gltf: gltf, bufferViewId: bufferViewId, gltfResource: loader._gltfResource, @@ -727,11 +818,13 @@ function loadAccessor(loader, gltf, accessorId, useQuaternion) { const bufferViewId = accessor.bufferView; if (defined(bufferViewId)) { - const bufferViewLoader = loadBufferView(loader, gltf, bufferViewId); - const promise = bufferViewLoader.promise.then(function (bufferViewLoader) { + const bufferViewLoader = getBufferViewLoader(loader, gltf, bufferViewId); + const promise = (async () => { + await bufferViewLoader.load(); if (loader.isDestroyed()) { return; } + const bufferViewTypedArray = bufferViewLoader.typedArray; const typedArray = getPackedTypedArray( gltf, @@ -741,7 +834,8 @@ function loadAccessor(loader, gltf, accessorId, useQuaternion) { useQuaternion = defaultValue(useQuaternion, false); loadAccessorValues(accessor, typedArray, values, useQuaternion); - }); + })(); + loader._loaderPromises.push(promise); return values; @@ -1062,7 +1156,7 @@ function loadAttribute( return attribute; } - const vertexBufferLoader = loadVertexBuffer( + const vertexBufferLoader = getVertexBufferLoader( loader, gltf, accessorId, @@ -1072,13 +1166,15 @@ function loadAttribute( loadTypedArray, frameState ); - const promise = vertexBufferLoader.promise.then(function ( - vertexBufferLoader - ) { - if (loader.isDestroyed()) { - return; - } + const index = loader._geometryLoaders.length; + loader._geometryLoaders.push(vertexBufferLoader); + const promise = vertexBufferLoader.load(); + loader._loaderPromises.push(promise); + // This can only execute once vertexBufferLoader.process() has run and returns true + // Save this finish callback by the loader index so it can be called + // in process(). + loader._geometryCallbacks[index] = () => { if ( defined(draco) && defined(draco.attributes) && @@ -1100,9 +1196,7 @@ function loadAttribute( loadTypedArray ); } - }); - - loader._loaderPromises.push(promise); + }; return attribute; } @@ -1276,7 +1370,7 @@ function loadIndices( const loadBuffer = needsPostProcessing ? false : outputBuffer; const loadTypedArray = needsPostProcessing ? true : outputTypedArray; - const indexBufferLoader = loadIndexBuffer( + const indexBufferLoader = getIndexBufferLoader( loader, gltf, accessorId, @@ -1286,18 +1380,18 @@ function loadIndices( frameState ); - const promise = indexBufferLoader.promise.then(function (indexBufferLoader) { - if (loader.isDestroyed()) { - return; - } - + const index = loader._geometryLoaders.length; + loader._geometryLoaders.push(indexBufferLoader); + const promise = indexBufferLoader.load(); + loader._loaderPromises.push(promise); + // This can only execute once indexBufferLoader.process() has run and returns true + // Save this finish callback by the loader index so it can be called + // in process(). + loader._geometryCallbacks[index] = () => { indices.indexDatatype = indexBufferLoader.indexDatatype; - indices.buffer = indexBufferLoader.buffer; indices.typedArray = indexBufferLoader.typedArray; - }); - - loader._loaderPromises.push(promise); + }; const indicesPlan = new PrimitiveLoadPlan.IndicesLoadPlan(indices); indicesPlan.loadBuffer = outputBuffer; @@ -1324,7 +1418,7 @@ function loadTexture( return undefined; } - const textureLoader = ResourceCache.loadTexture({ + const textureLoader = ResourceCache.getTextureLoader({ gltf: gltf, textureInfo: textureInfo, gltfResource: loader._gltfResource, @@ -1334,23 +1428,37 @@ function loadTexture( asynchronous: loader._asynchronous, }); - loader._textureLoaders.push(textureLoader); - const textureReader = GltfLoaderUtil.createModelTextureReader({ textureInfo: textureInfo, }); - const promise = textureLoader.promise.then(function (textureLoader) { - if (loader.isUnloaded() || loader.isDestroyed()) { + const index = loader._textureLoaders.length; + loader._textureLoaders.push(textureLoader); + const promise = textureLoader.load().catch((error) => { + if (loader.isDestroyed()) { return; } + + if (!loader._incrementallyLoadTextures) { + // If incrementallyLoadTextures is false, throw the error to ensure the loader state + // immediately is set to have failed + throw error; + } + + // Otherwise, save the error so it can be thrown next + loader._textureState = GltfLoaderState.FAILED; + loader._textureErrors.push(error); + }); + loader._texturesPromises.push(promise); + // This can only execute once textureLoader.process() has run and returns true + // Save this finish callback by the loader index so it can be called + // in process(). + loader._textureCallbacks[index] = () => { textureReader.texture = textureLoader.texture; if (defined(samplerOverride)) { textureReader.texture.sampler = samplerOverride; } - }); - - loader._texturesPromises.push(promise); + }; return textureReader; } @@ -2246,7 +2354,7 @@ function loadSkins(loader, gltf, nodes) { return skins; } -function loadStructuralMetadata( +async function loadStructuralMetadata( loader, gltf, extension, @@ -2264,11 +2372,8 @@ function loadStructuralMetadata( frameState: frameState, asynchronous: loader._asynchronous, }); - structuralMetadataLoader.load(); - loader._structuralMetadataLoader = structuralMetadataLoader; - - return structuralMetadataLoader; + return structuralMetadataLoader.load(); } function loadAnimationSampler(loader, gltf, gltfSampler) { @@ -2442,32 +2547,7 @@ function loadScene(gltf, nodes) { const scratchCenter = new Cartesian3(); -function parse( - loader, - gltf, - supportedImageFormats, - frameState, - rejectPromise, - rejectTexturesPromise -) { - const version = gltf.asset.version; - if (version !== "1.0" && version !== "2.0") { - const url = loader._gltfResource.url; - throw new RuntimeError( - `Failed to load ${url}: \nUnsupported glTF version: ${version}` - ); - } - const extensionsRequired = gltf.extensionsRequired; - if (defined(extensionsRequired)) { - ModelUtility.checkSupportedExtensions(extensionsRequired); - - // Check for the KHR_mesh_quantization extension here, it will be used later - // in loadAttribute(). - loader._hasKhrMeshQuantization = extensionsRequired.includes( - "KHR_mesh_quantization" - ); - } - +function parse(loader, gltf, supportedImageFormats, frameState) { const extensions = defaultValue(gltf.extensions, defaultValue.EMPTY_OBJECT); const structuralMetadataExtension = extensions.EXT_structural_metadata; const featureMetadataExtensionLegacy = extensions.EXT_feature_metadata; @@ -2535,7 +2615,7 @@ function parse( defined(structuralMetadataExtension) || defined(featureMetadataExtensionLegacy) ) { - const structuralMetadataLoader = loadStructuralMetadata( + const promise = loadStructuralMetadata( loader, gltf, structuralMetadataExtension, @@ -2543,46 +2623,24 @@ function parse( supportedImageFormats, frameState ); - const promise = structuralMetadataLoader.promise.then(function ( - structuralMetadataLoader - ) { - if (loader.isDestroyed()) { - return; - } - components.structuralMetadata = - structuralMetadataLoader.structuralMetadata; - }); loader._loaderPromises.push(promise); } - // Gather promises and reject if any promises fail. + // Gather promises and handle any errors const readyPromises = []; readyPromises.push.apply(readyPromises, loader._loaderPromises); + // When incrementallyLoadTextures is true, the errors are caught and thrown individually + // since it doesn't affect the overall loader state if (!loader._incrementallyLoadTextures) { readyPromises.push.apply(readyPromises, loader._texturesPromises); } - Promise.all(readyPromises) - .then(function () { - if (loader.isDestroyed()) { - return; - } - loader._state = GltfLoaderState.POST_PROCESSING; - }) - .catch(rejectPromise); - - // Separate promise will resolve once textures are loaded. - Promise.all(loader._texturesPromises) - .then(function () { - if (loader.isDestroyed()) { - return; - } - - // post processing only applies for geometry - loader._textureState = GltfLoaderState.PROCESSED; - }) - .catch(rejectTexturesPromise); + Promise.all(readyPromises).catch((error) => { + // This error will be thrown synchronously during the next call + // to process() + loader._processError = error; + }); } function unloadTextures(loader) { @@ -2594,7 +2652,7 @@ function unloadTextures(loader) { loader._textureLoaders.length = 0; } -function unloadBufferViews(loader) { +function unloadBufferViewLoaders(loader) { const bufferViewLoaders = loader._bufferViewLoaders; const bufferViewLoadersLength = bufferViewLoaders.length; for (let i = 0; i < bufferViewLoadersLength; ++i) { @@ -2644,13 +2702,13 @@ GltfLoader.prototype.isUnloaded = function () { * @private */ GltfLoader.prototype.unload = function () { - if (defined(this._gltfJsonLoader)) { + if (defined(this._gltfJsonLoader) && !this._gltfJsonLoader.isDestroyed()) { ResourceCache.unload(this._gltfJsonLoader); } this._gltfJsonLoader = undefined; unloadTextures(this); - unloadBufferViews(this); + unloadBufferViewLoaders(this); unloadGeometry(this); unloadGeneratedAttributes(this); unloadStructuralMetadata(this); diff --git a/packages/engine/Source/Scene/GltfStructuralMetadataLoader.js b/packages/engine/Source/Scene/GltfStructuralMetadataLoader.js index 032bcdf3f772..c320f05b31dd 100644 --- a/packages/engine/Source/Scene/GltfStructuralMetadataLoader.js +++ b/packages/engine/Source/Scene/GltfStructuralMetadataLoader.js @@ -68,7 +68,9 @@ function GltfStructuralMetadataLoader(options) { this._cacheKey = cacheKey; this._asynchronous = asynchronous; this._bufferViewLoaders = []; + this._bufferViewIds = []; this._textureLoaders = []; + this._textureIds = []; this._schemaLoader = undefined; this._structuralMetadata = undefined; this._state = ResourceLoaderState.UNLOADED; @@ -83,20 +85,6 @@ if (defined(Object.create)) { } Object.defineProperties(GltfStructuralMetadataLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. - * - * @memberof GltfStructuralMetadataLoader.prototype - * - * @type {Promise.|undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -133,55 +121,39 @@ Object.defineProperties(GltfStructuralMetadataLoader.prototype, { * @private */ GltfStructuralMetadataLoader.prototype.load = function () { - const bufferViewsPromise = loadBufferViews(this); - const texturesPromise = loadTextures(this); - const schemaPromise = loadSchema(this); + if (defined(this._promise)) { + return this._promise; + } - this._gltf = undefined; // No longer need to hold onto the glTF this._state = ResourceLoaderState.LOADING; + this._promise = (async () => { + try { + const bufferViewsPromise = loadBufferViews(this); + const texturesPromise = loadTextures(this); + const schemaPromise = loadSchema(this); - const that = this; + await Promise.all([bufferViewsPromise, texturesPromise, schemaPromise]); - this._promise = Promise.all([ - bufferViewsPromise, - texturesPromise, - schemaPromise, - ]) - .then(function (results) { - if (that.isDestroyed()) { + if (this.isDestroyed()) { return; } - const bufferViews = results[0]; - const textures = results[1]; - const schema = results[2]; - - if (defined(that._extension)) { - that._structuralMetadata = parseStructuralMetadata({ - extension: that._extension, - schema: schema, - bufferViews: bufferViews, - textures: textures, - }); - } else { - that._structuralMetadata = parseFeatureMetadataLegacy({ - extension: that._extensionLegacy, - schema: schema, - bufferViews: bufferViews, - textures: textures, - }); - } - that._state = ResourceLoaderState.READY; - return that; - }) - .catch(function (error) { - if (that.isDestroyed()) { + + this._gltf = undefined; // No longer need to hold onto the glTF + + this._state = ResourceLoaderState.LOADED; + return this; + } catch (error) { + if (this.isDestroyed()) { return; } - that.unload(); - that._state = ResourceLoaderState.FAILED; + + this.unload(); + this._state = ResourceLoaderState.FAILED; const errorMessage = "Failed to load structural metadata"; - return Promise.reject(that.getError(errorMessage, error)); - }); + throw this.getError(errorMessage, error); + } + })(); + return this._promise; }; function gatherBufferViewIdsFromProperties(properties, bufferViewIdSet) { @@ -261,7 +233,7 @@ function gatherUsedBufferViewIdsLegacy(extensionLegacy) { return bufferViewIdSet; } -function loadBufferViews(structuralMetadataLoader) { +async function loadBufferViews(structuralMetadataLoader) { let bufferViewIds; if (defined(structuralMetadataLoader._extension)) { bufferViewIds = gatherUsedBufferViewIds( @@ -275,40 +247,23 @@ function loadBufferViews(structuralMetadataLoader) { // Load the buffer views const bufferViewPromises = []; - const bufferViewLoaders = {}; for (const bufferViewId in bufferViewIds) { if (bufferViewIds.hasOwnProperty(bufferViewId)) { - const bufferViewLoader = ResourceCache.loadBufferView({ + const bufferViewLoader = ResourceCache.getBufferViewLoader({ gltf: structuralMetadataLoader._gltf, bufferViewId: parseInt(bufferViewId), gltfResource: structuralMetadataLoader._gltfResource, baseResource: structuralMetadataLoader._baseResource, }); - bufferViewPromises.push(bufferViewLoader.promise); + structuralMetadataLoader._bufferViewLoaders.push(bufferViewLoader); - bufferViewLoaders[bufferViewId] = bufferViewLoader; - } - } + structuralMetadataLoader._bufferViewIds.push(bufferViewId); - // Return a promise to a map of buffer view IDs to typed arrays - return Promise.all(bufferViewPromises).then(function () { - const bufferViews = {}; - for (const bufferViewId in bufferViewLoaders) { - if (bufferViewLoaders.hasOwnProperty(bufferViewId)) { - const bufferViewLoader = bufferViewLoaders[bufferViewId]; - // Copy the typed array and let the underlying ArrayBuffer be freed - const bufferViewTypedArray = new Uint8Array( - bufferViewLoader.typedArray - ); - bufferViews[bufferViewId] = bufferViewTypedArray; - } + bufferViewPromises.push(bufferViewLoader.load()); } + } - // Buffer views can be unloaded after the data has been copied - unloadBufferViews(structuralMetadataLoader); - - return bufferViews; - }); + return Promise.all(bufferViewPromises); } function gatherUsedTextureIds(structuralMetadataExtension) { @@ -385,10 +340,9 @@ function loadTextures(structuralMetadataLoader) { // Load the textures const texturePromises = []; - const textureLoaders = {}; for (const textureId in textureIds) { if (textureIds.hasOwnProperty(textureId)) { - const textureLoader = ResourceCache.loadTexture({ + const textureLoader = ResourceCache.getTextureLoader({ gltf: gltf, textureInfo: textureIds[textureId], gltfResource: gltfResource, @@ -397,26 +351,16 @@ function loadTextures(structuralMetadataLoader) { frameState: frameState, asynchronous: asynchronous, }); - texturePromises.push(textureLoader.promise); structuralMetadataLoader._textureLoaders.push(textureLoader); - textureLoaders[textureId] = textureLoader; + structuralMetadataLoader._textureIds.push(textureId); + texturePromises.push(textureLoader.load()); } } - // Return a promise to a map of texture IDs to Texture objects - return Promise.all(texturePromises).then(function () { - const textures = {}; - for (const textureId in textureLoaders) { - if (textureLoaders.hasOwnProperty(textureId)) { - const textureLoader = textureLoaders[textureId]; - textures[textureId] = textureLoader.texture; - } - } - return textures; - }); + return Promise.all(texturePromises); } -function loadSchema(structuralMetadataLoader) { +async function loadSchema(structuralMetadataLoader) { const extension = defaultValue( structuralMetadataLoader._extension, structuralMetadataLoader._extensionLegacy @@ -427,20 +371,20 @@ function loadSchema(structuralMetadataLoader) { const resource = structuralMetadataLoader._baseResource.getDerivedResource({ url: extension.schemaUri, }); - schemaLoader = ResourceCache.loadSchema({ + schemaLoader = ResourceCache.getSchemaLoader({ resource: resource, }); } else { - schemaLoader = ResourceCache.loadSchema({ + schemaLoader = ResourceCache.getSchemaLoader({ schema: extension.schema, }); } structuralMetadataLoader._schemaLoader = schemaLoader; - - return schemaLoader.promise.then(function (schemaLoader) { + await schemaLoader.load(); + if (!schemaLoader.isDestroyed()) { return schemaLoader.schema; - }); + } } /** @@ -454,17 +398,68 @@ GltfStructuralMetadataLoader.prototype.process = function (frameState) { Check.typeOf.object("frameState", frameState); //>>includeEnd('debug'); - if (this._state !== ResourceLoaderState.LOADING) { - return; + if (this._state === ResourceLoaderState.READY) { + return true; + } + + if (this._state !== ResourceLoaderState.LOADED) { + return false; } const textureLoaders = this._textureLoaders; const textureLoadersLength = textureLoaders.length; - + let ready = true; for (let i = 0; i < textureLoadersLength; ++i) { const textureLoader = textureLoaders[i]; - textureLoader.process(frameState); + const textureReady = textureLoader.process(frameState); + ready = ready && textureReady; + } + + if (!ready) { + return false; + } + + const schema = this._schemaLoader.schema; + const bufferViews = {}; + for (let i = 0; i < this._bufferViewIds.length; ++i) { + const bufferViewId = this._bufferViewIds[i]; + const bufferViewLoader = this._bufferViewLoaders[i]; + if (!bufferViewLoader.isDestroyed()) { + // Copy the typed array and let the underlying ArrayBuffer be freed + const bufferViewTypedArray = new Uint8Array(bufferViewLoader.typedArray); + bufferViews[bufferViewId] = bufferViewTypedArray; + } } + + const textures = {}; + for (let i = 0; i < this._textureIds.length; ++i) { + const textureId = this._textureIds[i]; + const textureLoader = textureLoaders[i]; + if (!textureLoader.isDestroyed()) { + textures[textureId] = textureLoader.texture; + } + } + if (defined(this._extension)) { + this._structuralMetadata = parseStructuralMetadata({ + extension: this._extension, + schema: schema, + bufferViews: bufferViews, + textures: textures, + }); + } else { + this._structuralMetadata = parseFeatureMetadataLegacy({ + extension: this._extensionLegacy, + schema: schema, + bufferViews: bufferViews, + textures: textures, + }); + } + + // Buffer views can be unloaded after the data has been copied + unloadBufferViews(this); + + this._state = ResourceLoaderState.READY; + return true; }; function unloadBufferViews(structuralMetadataLoader) { @@ -474,6 +469,7 @@ function unloadBufferViews(structuralMetadataLoader) { ResourceCache.unload(bufferViewLoaders[i]); } structuralMetadataLoader._bufferViewLoaders.length = 0; + structuralMetadataLoader._bufferViewIds.length = 0; } function unloadTextures(structuralMetadataLoader) { @@ -483,6 +479,7 @@ function unloadTextures(structuralMetadataLoader) { ResourceCache.unload(textureLoaders[i]); } structuralMetadataLoader._textureLoaders.length = 0; + structuralMetadataLoader._textureIds.length = 0; } /** diff --git a/packages/engine/Source/Scene/GltfTextureLoader.js b/packages/engine/Source/Scene/GltfTextureLoader.js index 4c6139914c95..a71a36448d8f 100644 --- a/packages/engine/Source/Scene/GltfTextureLoader.js +++ b/packages/engine/Source/Scene/GltfTextureLoader.js @@ -78,7 +78,6 @@ function GltfTextureLoader(options) { this._texture = undefined; this._state = ResourceLoaderState.UNLOADED; this._promise = undefined; - this._process = function (loader, frameState) {}; } if (defined(Object.create)) { @@ -87,20 +86,6 @@ if (defined(Object.create)) { } Object.defineProperties(GltfTextureLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. - * - * @memberof GltfTextureLoader.prototype - * - * @type {Promise.|undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -138,86 +123,45 @@ const scratchTextureJob = new CreateTextureJob(); * @returns {Promise.} A promise which resolves to the loader when the resource loading is completed. * @private */ -GltfTextureLoader.prototype.load = function () { - const resourceCache = this._resourceCache; - const imageLoader = resourceCache.loadImage({ - gltf: this._gltf, - imageId: this._imageId, - gltfResource: this._gltfResource, - baseResource: this._baseResource, - }); +GltfTextureLoader.prototype.load = async function () { + if (defined(this._promise)) { + return this._promise; + } - this._imageLoader = imageLoader; this._state = ResourceLoaderState.LOADING; - const that = this; - const processPromise = new Promise(function (resolve) { - that._process = function (loader, frameState) { - if (defined(loader._texture)) { - // Already created texture - return; - } - - if (!defined(loader._image)) { - // Not ready to create texture + const resourceCache = this._resourceCache; + this._promise = (async () => { + try { + const imageLoader = resourceCache.getImageLoader({ + gltf: this._gltf, + imageId: this._imageId, + gltfResource: this._gltfResource, + baseResource: this._baseResource, + }); + this._imageLoader = imageLoader; + await imageLoader.load(); + + if (this.isDestroyed()) { return; } - let texture; - - if (loader._asynchronous) { - const textureJob = scratchTextureJob; - textureJob.set( - loader._gltf, - loader._textureInfo, - loader._image, - loader._mipLevels, - frameState.context - ); - const jobScheduler = frameState.jobScheduler; - if (!jobScheduler.execute(textureJob, JobType.TEXTURE)) { - // Job scheduler is full. Try again next frame. - return; - } - texture = textureJob.texture; - } else { - texture = createTexture( - loader._gltf, - loader._textureInfo, - loader._image, - loader._mipLevels, - frameState.context - ); - } - - // Unload everything except the texture - loader.unload(); - - loader._texture = texture; - loader._state = ResourceLoaderState.READY; - resolve(loader); - }; - }); - - this._promise = imageLoader.promise - .then(function () { - if (that.isDestroyed()) { - return; - } // Now wait for process() to run to finish loading - that._image = imageLoader.image; - that._mipLevels = imageLoader.mipLevels; - that._state = ResourceLoaderState.PROCESSING; - return processPromise; - }) - .catch(function (error) { - if (that.isDestroyed()) { + this._image = imageLoader.image; + this._mipLevels = imageLoader.mipLevels; + this._state = ResourceLoaderState.LOADED; + + return this; + } catch (error) { + if (this.isDestroyed()) { return; } - that.unload(); - that._state = ResourceLoaderState.FAILED; + + this.unload(); + this._state = ResourceLoaderState.FAILED; const errorMessage = "Failed to load texture"; - return Promise.reject(that.getError(errorMessage, error)); - }); + throw this.getError(errorMessage, error); + } + })(); return this._promise; }; @@ -350,6 +294,7 @@ function createTexture(gltf, textureInfo, image, mipLevels, context) { * Processes the resource until it becomes ready. * * @param {FrameState} frameState The frame state. + * @returns {boolean} true once all resourced are ready. * @private */ GltfTextureLoader.prototype.process = function (frameState) { @@ -357,7 +302,62 @@ GltfTextureLoader.prototype.process = function (frameState) { Check.typeOf.object("frameState", frameState); //>>includeEnd('debug'); - return this._process(this, frameState); + if (this._state === ResourceLoaderState.READY) { + return true; + } + + if ( + this._state !== ResourceLoaderState.LOADED && + this._state !== ResourceLoaderState.PROCESSING + ) { + return false; + } + + if (defined(this._texture)) { + // Already created texture + return false; + } + + if (!defined(this._image)) { + // Not ready to create texture + return false; + } + + this._state = ResourceLoaderState.PROCESSING; + + let texture; + if (this._asynchronous) { + const textureJob = scratchTextureJob; + textureJob.set( + this._gltf, + this._textureInfo, + this._image, + this._mipLevels, + frameState.context + ); + const jobScheduler = frameState.jobScheduler; + if (!jobScheduler.execute(textureJob, JobType.TEXTURE)) { + // Job scheduler is full. Try again next frame. + return; + } + texture = textureJob.texture; + } else { + texture = createTexture( + this._gltf, + this._textureInfo, + this._image, + this._mipLevels, + frameState.context + ); + } + + // Unload everything except the texture + this.unload(); + + this._texture = texture; + this._state = ResourceLoaderState.READY; + this._resourceCache.statistics.addTextureLoader(this); + return true; }; /** diff --git a/packages/engine/Source/Scene/GltfVertexBufferLoader.js b/packages/engine/Source/Scene/GltfVertexBufferLoader.js index 425d3bad123e..a2769521a398 100644 --- a/packages/engine/Source/Scene/GltfVertexBufferLoader.js +++ b/packages/engine/Source/Scene/GltfVertexBufferLoader.js @@ -115,7 +115,6 @@ function GltfVertexBufferLoader(options) { this._buffer = undefined; this._state = ResourceLoaderState.UNLOADED; this._promise = undefined; - this._process = function (loader, frameState) {}; } if (defined(Object.create)) { @@ -124,20 +123,6 @@ if (defined(Object.create)) { } Object.defineProperties(GltfVertexBufferLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. - * - * @memberof GltfVertexBufferLoader.prototype - * - * @type {Promise.|undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -209,77 +194,17 @@ function hasDracoCompression(draco, semantic) { * @returns {Promise.} A promise which resolves to the loader when the resource loading is completed. * @private */ -GltfVertexBufferLoader.prototype.load = function () { - let promise; +GltfVertexBufferLoader.prototype.load = async function () { + if (defined(this._promise)) { + return this._promise; + } if (hasDracoCompression(this._draco, this._attributeSemantic)) { - promise = loadFromDraco(this); - } else { - promise = loadFromBufferView(this); + this._promise = loadFromDraco(this); + return this._promise; } - const that = this; - const scratchVertexBufferJob = new CreateVertexBufferJob(); - const processPromise = new Promise(function (resolve) { - that._process = function (loader, frameState) { - if (loader._state === ResourceLoaderState.READY) { - return; - } - - const typedArray = loader._typedArray; - - if (defined(loader._dracoLoader)) { - loader._dracoLoader.process(frameState); - } - - if (defined(loader._bufferViewLoader)) { - loader._bufferViewLoader.process(frameState); - } - - if (!defined(typedArray)) { - // Buffer view hasn't been loaded yet - return; - } - - let buffer; - if (loader._loadBuffer && loader._asynchronous) { - const vertexBufferJob = scratchVertexBufferJob; - vertexBufferJob.set(typedArray, frameState.context); - const jobScheduler = frameState.jobScheduler; - if (!jobScheduler.execute(vertexBufferJob, JobType.BUFFER)) { - // Job scheduler is full. Try again next frame. - return; - } - buffer = vertexBufferJob.buffer; - } else if (loader._loadBuffer) { - buffer = createVertexBuffer(typedArray, frameState.context); - } - - // Unload everything except the vertex buffer - loader.unload(); - - loader._buffer = buffer; - loader._typedArray = loader._loadTypedArray ? typedArray : undefined; - loader._state = ResourceLoaderState.READY; - resolve(loader); - }; - }); - - this._promise = promise - .then(function () { - if (that.isDestroyed()) { - return; - } - - return processPromise; - }) - .catch(function (error) { - if (that.isDestroyed()) { - return; - } - - return handleError(that, error); - }); + this._promise = loadFromBufferView(this); return this._promise; }; @@ -335,79 +260,98 @@ function getQuantizationInformation( return quantization; } -function loadFromDraco(vertexBufferLoader) { - const resourceCache = vertexBufferLoader._resourceCache; - const dracoLoader = resourceCache.loadDraco({ - gltf: vertexBufferLoader._gltf, - draco: vertexBufferLoader._draco, - gltfResource: vertexBufferLoader._gltfResource, - baseResource: vertexBufferLoader._baseResource, - }); - - vertexBufferLoader._dracoLoader = dracoLoader; +async function loadFromDraco(vertexBufferLoader) { vertexBufferLoader._state = ResourceLoaderState.LOADING; + const resourceCache = vertexBufferLoader._resourceCache; + try { + const dracoLoader = resourceCache.getDracoLoader({ + gltf: vertexBufferLoader._gltf, + draco: vertexBufferLoader._draco, + gltfResource: vertexBufferLoader._gltfResource, + baseResource: vertexBufferLoader._baseResource, + }); + vertexBufferLoader._dracoLoader = dracoLoader; + await dracoLoader.load(); - return dracoLoader.promise.then(function () { if (vertexBufferLoader.isDestroyed()) { return; } - // Get the typed array and quantization information - const decodedVertexAttributes = dracoLoader.decodedData.vertexAttributes; - const attributeSemantic = vertexBufferLoader._attributeSemantic; - const dracoAttribute = decodedVertexAttributes[attributeSemantic]; - const accessorId = vertexBufferLoader._accessorId; - const accessor = vertexBufferLoader._gltf.accessors[accessorId]; - const type = accessor.type; - const typedArray = dracoAttribute.array; - const dracoQuantization = dracoAttribute.data.quantization; - if (defined(dracoQuantization)) { - vertexBufferLoader._quantization = getQuantizationInformation( - dracoQuantization, - dracoAttribute.data.componentDatatype, - dracoAttribute.data.componentsPerAttribute, - type - ); - } // Now wait for process() to run to finish loading - vertexBufferLoader._typedArray = new Uint8Array( - typedArray.buffer, - typedArray.byteOffset, - typedArray.byteLength - ); - vertexBufferLoader._state = ResourceLoaderState.PROCESSING; + vertexBufferLoader._state = ResourceLoaderState.LOADED; return vertexBufferLoader; - }); + } catch { + if (vertexBufferLoader.isDestroyed()) { + return; + } + + handleError(vertexBufferLoader); + } } -function loadFromBufferView(vertexBufferLoader) { - const resourceCache = vertexBufferLoader._resourceCache; - const bufferViewLoader = resourceCache.loadBufferView({ - gltf: vertexBufferLoader._gltf, - bufferViewId: vertexBufferLoader._bufferViewId, - gltfResource: vertexBufferLoader._gltfResource, - baseResource: vertexBufferLoader._baseResource, - }); +function processDraco(vertexBufferLoader) { + vertexBufferLoader._state = ResourceLoaderState.PROCESSING; + const dracoLoader = vertexBufferLoader._dracoLoader; + + // Get the typed array and quantization information + const decodedVertexAttributes = dracoLoader.decodedData.vertexAttributes; + const attributeSemantic = vertexBufferLoader._attributeSemantic; + const dracoAttribute = decodedVertexAttributes[attributeSemantic]; + const accessorId = vertexBufferLoader._accessorId; + const accessor = vertexBufferLoader._gltf.accessors[accessorId]; + const type = accessor.type; + const typedArray = dracoAttribute.array; + const dracoQuantization = dracoAttribute.data.quantization; + if (defined(dracoQuantization)) { + vertexBufferLoader._quantization = getQuantizationInformation( + dracoQuantization, + dracoAttribute.data.componentDatatype, + dracoAttribute.data.componentsPerAttribute, + type + ); + } + + vertexBufferLoader._typedArray = new Uint8Array( + typedArray.buffer, + typedArray.byteOffset, + typedArray.byteLength + ); +} + +async function loadFromBufferView(vertexBufferLoader) { vertexBufferLoader._state = ResourceLoaderState.LOADING; - vertexBufferLoader._bufferViewLoader = bufferViewLoader; + const resourceCache = vertexBufferLoader._resourceCache; + try { + const bufferViewLoader = resourceCache.getBufferViewLoader({ + gltf: vertexBufferLoader._gltf, + bufferViewId: vertexBufferLoader._bufferViewId, + gltfResource: vertexBufferLoader._gltfResource, + baseResource: vertexBufferLoader._baseResource, + }); + vertexBufferLoader._bufferViewLoader = bufferViewLoader; + await bufferViewLoader.load(); - return bufferViewLoader.promise.then(function () { if (vertexBufferLoader.isDestroyed()) { return; } - // Now wait for process() to run to finish loading + vertexBufferLoader._typedArray = bufferViewLoader.typedArray; vertexBufferLoader._state = ResourceLoaderState.PROCESSING; return vertexBufferLoader; - }); + } catch (error) { + if (vertexBufferLoader.isDestroyed()) { + return; + } + + handleError(vertexBufferLoader, error); + } } function handleError(vertexBufferLoader, error) { vertexBufferLoader.unload(); vertexBufferLoader._state = ResourceLoaderState.FAILED; const errorMessage = "Failed to load vertex buffer"; - error = vertexBufferLoader.getError(errorMessage, error); - return Promise.reject(error); + throw vertexBufferLoader.getError(errorMessage, error); } function CreateVertexBufferJob() { @@ -435,6 +379,8 @@ function createVertexBuffer(typedArray, context) { return buffer; } +const scratchVertexBufferJob = new CreateVertexBufferJob(); + /** * Processes the resource until it becomes ready. * @@ -446,7 +392,53 @@ GltfVertexBufferLoader.prototype.process = function (frameState) { Check.typeOf.object("frameState", frameState); //>>includeEnd('debug'); - return this._process(this, frameState); + if (this._state === ResourceLoaderState.READY) { + return true; + } + + if ( + this._state !== ResourceLoaderState.LOADED && + this._state !== ResourceLoaderState.PROCESSING + ) { + return false; + } + + if (defined(this._dracoLoader)) { + try { + const ready = this._dracoLoader.process(frameState); + if (!ready) { + return false; + } + } catch (error) { + handleError(this, error); + } + + processDraco(this); + } + + let buffer; + const typedArray = this._typedArray; + if (this._loadBuffer && this._asynchronous) { + const vertexBufferJob = scratchVertexBufferJob; + vertexBufferJob.set(typedArray, frameState.context); + const jobScheduler = frameState.jobScheduler; + if (!jobScheduler.execute(vertexBufferJob, JobType.BUFFER)) { + // Job scheduler is full. Try again next frame. + return false; + } + buffer = vertexBufferJob.buffer; + } else if (this._loadBuffer) { + buffer = createVertexBuffer(typedArray, frameState.context); + } + + // Unload everything except the vertex buffer + this.unload(); + + this._buffer = buffer; + this._typedArray = this._loadTypedArray ? typedArray : undefined; + this._state = ResourceLoaderState.READY; + this._resourceCache.statistics.addGeometryLoader(this); + return true; }; /** diff --git a/packages/engine/Source/Scene/ImplicitSubtree.js b/packages/engine/Source/Scene/ImplicitSubtree.js index 9f0ec3cc8bad..3c7f585ba583 100644 --- a/packages/engine/Source/Scene/ImplicitSubtree.js +++ b/packages/engine/Source/Scene/ImplicitSubtree.js @@ -714,12 +714,12 @@ function requestExternalBuffer(subtree, bufferHeader) { url: bufferHeader.uri, }); - const bufferLoader = ResourceCache.loadExternalBuffer({ + const bufferLoader = ResourceCache.getExternalBufferLoader({ resource: bufferResource, }); subtree._bufferLoader = bufferLoader; - return bufferLoader.promise.then(function (bufferLoader) { + return bufferLoader.load().then(function (bufferLoader) { return bufferLoader.typedArray; }); } diff --git a/packages/engine/Source/Scene/MetadataSchemaLoader.js b/packages/engine/Source/Scene/MetadataSchemaLoader.js index be21b606d479..0aab97eb23af 100644 --- a/packages/engine/Source/Scene/MetadataSchemaLoader.js +++ b/packages/engine/Source/Scene/MetadataSchemaLoader.js @@ -52,20 +52,6 @@ if (defined(Object.create)) { } Object.defineProperties(MetadataSchemaLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. - * - * @memberof MetadataSchemaLoader.prototype - * - * @type {Promise.|undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -101,37 +87,41 @@ Object.defineProperties(MetadataSchemaLoader.prototype, { * @returns {Promise.} A promise which resolves to the loader when the resource loading is completed. * @private */ -MetadataSchemaLoader.prototype.load = function () { +MetadataSchemaLoader.prototype.load = async function () { + if (defined(this._promise)) { + return this._promise; + } + if (defined(this._schema)) { this._promise = Promise.resolve(this); - } else { - this._promise = loadExternalSchema(this); + return this._promise; } + this._promise = loadExternalSchema(this); return this._promise; }; -function loadExternalSchema(schemaLoader) { +async function loadExternalSchema(schemaLoader) { const resource = schemaLoader._resource; schemaLoader._state = ResourceLoaderState.LOADING; - return resource - .fetchJson() - .then(function (json) { - if (schemaLoader.isDestroyed()) { - return; - } - schemaLoader._schema = MetadataSchema.fromJson(json); - schemaLoader._state = ResourceLoaderState.READY; - return schemaLoader; - }) - .catch(function (error) { - if (schemaLoader.isDestroyed()) { - return; - } - schemaLoader._state = ResourceLoaderState.FAILED; - const errorMessage = `Failed to load schema: ${resource.url}`; - return Promise.reject(schemaLoader.getError(errorMessage, error)); - }); + try { + const json = await resource.fetchJson(); + if (schemaLoader.isDestroyed()) { + return; + } + + schemaLoader._schema = MetadataSchema.fromJson(json); + schemaLoader._state = ResourceLoaderState.READY; + return schemaLoader; + } catch (error) { + if (schemaLoader.isDestroyed()) { + return; + } + + schemaLoader._state = ResourceLoaderState.FAILED; + const errorMessage = `Failed to load schema: ${resource.url}`; + throw schemaLoader.getError(errorMessage, error); + } } /** diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index cc1888dc4e07..5500c8faa330 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -6,9 +6,11 @@ import Credit from "../../Core/Credit.js"; import Color from "../../Core/Color.js"; import defined from "../../Core/defined.js"; import defaultValue from "../../Core/defaultValue.js"; +import deprecationWarning from "../../Core/deprecationWarning.js"; import DeveloperError from "../../Core/DeveloperError.js"; import destroyObject from "../../Core/destroyObject.js"; import DistanceDisplayCondition from "../../Core/DistanceDisplayCondition.js"; +import Event from "../../Core/Event.js"; import Matrix3 from "../../Core/Matrix3.js"; import Matrix4 from "../../Core/Matrix4.js"; import Resource from "../../Core/Resource.js"; @@ -39,7 +41,7 @@ import StyleCommandsNeeded from "./StyleCommandsNeeded.js"; /** *
- * To construct a Model, call {@link Model.fromGltf}. Do not call the constructor directly. + * To construct a Model, call {@link Model.fromGltfAsync}. Do not call the constructor directly. *
* A 3D model based on glTF, the runtime asset format for WebGL, OpenGL ES, and OpenGL. *

@@ -155,7 +157,7 @@ import StyleCommandsNeeded from "./StyleCommandsNeeded.js"; * @privateParam {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this model. This cannot be set after the model has loaded. * - * @see Model.fromGltf + * @see Model.fromGltfAsync * * @demo {@link https://sandcastle.cesium.com/index.html?src=3D%20Models.html|Cesium Sandcastle Models Demo} */ @@ -455,9 +457,65 @@ function Model(options) { this._skipLevelOfDetail = false; this._ignoreCommands = defaultValue(options.ignoreCommands, false); - this._completeLoad = function (model, frameState) {}; this._texturesLoadedPromise = undefined; - this._readyPromise = initialize(this); + + this._completeLoad = undefined; + this._rejectLoad = undefined; + this._completeTexturesLoad = undefined; + this._rejectTexturesLoad = undefined; + + // This is for backward compatibility. When readyPromise is removed, this block can be too + if (!defined(this._loader._promise)) { + this._readyPromise = new Promise((resolve, reject) => { + this._completeLoad = () => { + resolve(this); + return false; + }; + this._rejectLoad = (error) => { + reject(error); + return false; + }; + }); + + // Transcoded .pnts models do not have textures + if (this._loader instanceof PntsLoader) { + this._texturesLoadedPromise = Promise.resolve(this); + } else { + this._texturesLoadedPromise = new Promise((resolve, reject) => { + this._completeTexturesLoad = () => { + resolve(this); + return false; + }; + this._rejectTexturesLoad = (error) => { + reject(error); + return false; + }; + }); + } + + this._loader.load().catch((error) => { + // If the model is destroyed before the promise resolves, then + // the loader will have been destroyed as well. Return early. + if ( + this.isDestroyed() || + !defined(this._loader) || + this._loader.isDestroyed() + ) { + return; + } + + this._rejectLoad = this._rejectLoad( + ModelUtility.getError("model", this._resource, error) + ); + }); + } else { + this._readyPromise = Promise.resolve(this); + this._texturesLoadedPromise = Promise.resolve(this); + } + + this._errorEvent = new Event(); + this._readyEvent = new Event(); + this._texturesReadyEvent = new Event(); this._sceneGraph = undefined; this._nodesByName = {}; // Stores the nodes by their names in the glTF. @@ -470,6 +528,15 @@ function Model(options) { this.pickObject = options.pickObject; } +function handleError(model, error) { + if (model._errorEvent.numberOfListeners > 0) { + model._errorEvent.raiseEvent(error); + return; + } + + console.error(error); +} + function createModelFeatureTables(model, structuralMetadata) { const featureTables = model._featureTables; @@ -561,101 +628,10 @@ function isColorAlphaDirty(currentColor, previousColor) { ); } -function initialize(model) { - const loader = model._loader; - const resource = model._resource; - - loader.load(); - - const loaderPromise = loader.promise.then(function (loader) { - // If the model is destroyed before the promise resolves, then - // the loader will have been destroyed as well. Return early. - if (!defined(loader)) { - return; - } - - const components = loader.components; - if (!defined(components)) { - if (loader.isUnloaded()) { - return; - } - - throw new RuntimeError("Failed to load model."); - } - - const structuralMetadata = components.structuralMetadata; - - if ( - defined(structuralMetadata) && - structuralMetadata.propertyTableCount > 0 - ) { - createModelFeatureTables(model, structuralMetadata); - } - - const sceneGraph = new ModelSceneGraph({ - model: model, - modelComponents: components, - }); - - model._sceneGraph = sceneGraph; - model._gltfCredits = sceneGraph.components.asset.credits; - - const resourceCredits = model._resource.credits; - if (defined(resourceCredits)) { - const length = resourceCredits.length; - for (let i = 0; i < length; i++) { - model._resourceCredits.push(resourceCredits[i]); - } - } - - model._resourcesLoaded = true; - }); - - // Transcoded .pnts models do not have textures - const texturesLoadedPromise = defaultValue( - loader.texturesLoadedPromise, - Promise.resolve() - ); - model._texturesLoadedPromise = texturesLoadedPromise - .then(function () { - // If the model was destroyed while loading textures, return. - if (!defined(model) || model.isDestroyed()) { - return; - } - - model._texturesLoaded = true; - - // Re-run the pipeline so texture memory statistics are re-computed - if (loader._incrementallyLoadTextures) { - model.resetDrawCommands(); - } - }) - .catch(ModelUtility.getFailedLoadFunction(model, "model", resource)); - - const promise = new Promise(function (resolve, reject) { - model._completeLoad = function (model, frameState) { - // Set the model as ready after the first frame render since the user might set up events subscribed to - // the post render event, and the model may not be ready for those past the first frame. - frameState.afterRender.push(function () { - model._ready = true; - resolve(model); - return true; - }); - }; - }); - - return loaderPromise - .then(function () { - return promise; - }) - .catch(ModelUtility.getFailedLoadFunction(model, "model", resource)); -} - Object.defineProperties(Model.prototype, { /** * When true, this model is ready to render, i.e., the external binary, image, - * and shader files were downloaded and the WebGL resources were created. This is set to - * true right before {@link Model#readyPromise} is resolved. + * and shader files were downloaded and the WebGL resources were created. * * @memberof Model.prototype * @@ -670,6 +646,54 @@ Object.defineProperties(Model.prototype, { }, }, + /** + * Gets an event that is raised when the model encounters an asynchronous rendering error. By subscribing + * to the event, you will be notified of the error and can potentially recover from it. Event listeners + * are passed an instance of {@link ModelError}. + * @memberof Model.prototype + * @type {Event} + * @readonly + */ + errorEvent: { + get: function () { + return this._errorEvent; + }, + }, + + /** + * Gets an event that is raised when the model is loaded and ready for rendering, i.e. when the external resources + * have been downloaded and the WebGL resources are created. Event listeners + * are passed an instance of the {@link Model}. + * + *

+ * If {@link Model.incrementallyLoadTextures} is true, this event will be raised before all textures are loaded and ready for rendering. Subscribe to {@link Model.texturesReadyEvent} to be notified when the textures are ready. + *

+ * + * @memberof Model.prototype + * @type {Event} + * @readonly + */ + readyEvent: { + get: function () { + return this._readyEvent; + }, + }, + + /** + * Gets an event that, if {@link Model.incrementallyLoadTextures} is true, is raised when the model textures are loaded and ready for rendering, i.e. when the external resources + * have been downloaded and the WebGL resources are created. Event listeners + * are passed an instance of the {@link Model}. + * + * @memberof Model.prototype + * @type {Event} + * @readonly + */ + texturesReadyEvent: { + get: function () { + return this._texturesReadyEvent; + }, + }, + /** * Gets the promise that will be resolved when this model is ready to render, i.e. when the external resources * have been downloaded and the WebGL resources are created. @@ -681,9 +705,14 @@ Object.defineProperties(Model.prototype, { * * @type {Promise.} * @readonly + * @deprecated */ readyPromise: { get: function () { + deprecationWarning( + "Model.readyPromise", + "Model.readyPromise was deprecated in CesiumJS 1.102. It will be removed in 1.104. Use Model.fromGltfAsync and Model.readyEvent instead." + ); return this._readyPromise; }, }, @@ -697,11 +726,16 @@ Object.defineProperties(Model.prototype, { * * @type {Promise} * @readonly + * @deprecated * * @private */ texturesLoadedPromise: { get: function () { + deprecationWarning( + "Model.texturesLoadedPromise", + "Model.texturesLoadedPromise was deprecated in CesiumJS 1.102. It will be removed in 1.104. Use Model.fromGltfAsync and Model.texturesReadyEvent instead." + ); return this._texturesLoadedPromise; }, }, @@ -1155,7 +1189,7 @@ Object.defineProperties(Model.prototype, { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError( - "The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true." + "The model is not loaded. Use Model.readyEvent or wait for Model.ready to be true." ); } //>>includeEnd('debug'); @@ -1223,7 +1257,7 @@ Object.defineProperties(Model.prototype, { ) { oneTimeWarning( "model-debug-wireframe-ignored", - "enableDebugWireframe must be set to true in Model.fromGltf, otherwise debugWireframe will be ignored." + "enableDebugWireframe must be set to true in Model.fromGltfAsync, otherwise debugWireframe will be ignored." ); } }, @@ -1664,7 +1698,7 @@ Object.defineProperties(Model.prototype, { * @param {String} name The name of the node in the glTF. * @returns {ModelNode} The node, or undefined if no node with the name exists. * - * @exception {DeveloperError} The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true. + * @exception {DeveloperError} The model is not loaded. Use Model.readyEvent or wait for Model.ready to be true. * * @example * // Apply non-uniform scale to node "Hand" @@ -1675,7 +1709,7 @@ Model.prototype.getNode = function (name) { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError( - "The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true." + "The model is not loaded. Use Model.readyEvent or wait for Model.ready to be true." ); } Check.typeOf.string("name", name); @@ -1692,7 +1726,7 @@ Model.prototype.getNode = function (name) { * @param {String} articulationStageKey The name of the articulation, a space, and the name of the stage. * @param {Number} value The numeric value of this stage of the articulation. * - * @exception {DeveloperError} The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true. + * @exception {DeveloperError} The model is not loaded. Use Model.readyEvent or wait for Model.ready to be true. * * @see Model#applyArticulations * @@ -1705,7 +1739,7 @@ Model.prototype.setArticulationStage = function (articulationStageKey, value) { Check.typeOf.number("value", value); if (!this._ready) { throw new DeveloperError( - "The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true." + "The model is not loaded. Use Model.readyEvent or wait for Model.ready to be true." ); } //>>includeEnd('debug'); @@ -1718,13 +1752,13 @@ Model.prototype.setArticulationStage = function (articulationStageKey, value) { * participates in any articulation. Note that this will overwrite any node * transformations on participating nodes. * - * @exception {DeveloperError} The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true. + * @exception {DeveloperError} The model is not loaded. Use Model.readyEvent or wait for Model.ready to be true. */ Model.prototype.applyArticulations = function () { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError( - "The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true." + "The model is not loaded. Use Model.readyEvent or wait for Model.ready to be true." ); } //>>includeEnd('debug'); @@ -1764,10 +1798,40 @@ const scratchClippingPlanesMatrix = new Matrix4(); * @exception {RuntimeError} Failed to load external reference. */ Model.prototype.update = function (frameState) { - // Keep processing the model every frame until the main resources - // (buffer views) and textures (which may be loaded asynchronously) - // are processed. - processLoader(this, frameState); + let finishedProcessing = false; + try { + // Keep processing the model every frame until the main resources + // (buffer views) and textures (which may be loaded asynchronously) + // are processed. + finishedProcessing = processLoader(this, frameState); + } catch (error) { + if (!this._loader.incrementallyLoadTextures) { + const runtimeError = ModelUtility.getError( + "model", + this._resource, + error + ); + handleError(this, runtimeError); + + this._rejectLoad = this._rejectLoad && this._rejectLoad(runtimeError); + this._rejectTexturesLoad = + this._rejectTexturesLoad && this._rejectTexturesLoad(runtimeError); + } else if (error.name === "TextureError") { + handleError(this, error); + + this._rejectTexturesLoad = + this._rejectTexturesLoad && this._rejectTexturesLoad(error); + } else { + const runtimeError = ModelUtility.getError( + "model", + this._resource, + error + ); + handleError(this, runtimeError); + + this._rejectLoad = this._rejectLoad && this._rejectLoad(runtimeError); + } + } // A custom shader may have to load texture uniforms. updateCustomShader(this, frameState); @@ -1776,6 +1840,41 @@ Model.prototype.update = function (frameState) { // for specular maps. updateImageBasedLighting(this, frameState); + if (!this._resourcesLoaded && finishedProcessing) { + this._resourcesLoaded = true; + + const components = this._loader.components; + if (!defined(components)) { + if (this._loader.isUnloaded()) { + return; + } + + const error = ModelUtility.getError( + "model", + this._resource, + new RuntimeError("Failed to load model.") + ); + handleError(error); + this._rejectLoad = this._rejectLoad && this._rejectLoad(error); + } + + const structuralMetadata = components.structuralMetadata; + if ( + defined(structuralMetadata) && + structuralMetadata.propertyTableCount > 0 + ) { + createModelFeatureTables(this, structuralMetadata); + } + + const sceneGraph = new ModelSceneGraph({ + model: this, + modelComponents: components, + }); + + this._sceneGraph = sceneGraph; + this._gltfCredits = sceneGraph.components.asset.credits; + } + // Short-circuit if the model resources aren't ready or the scene // is currently morphing. if (!this._resourcesLoaded || frameState.mode === SceneMode.MORPHING) { @@ -1806,14 +1905,37 @@ Model.prototype.update = function (frameState) { // This check occurs after the bounding sphere has been updated so that // zooming to the bounding sphere can account for any modifications // from the clamp-to-ground setting. - const model = this; - if (!model._ready) { - model._completeLoad(model, frameState); + if (!this._ready) { + // Set the model as ready after the first frame render since the user might set up events subscribed to + // the post render event, and the model may not be ready for those past the first frame. + frameState.afterRender.push(() => { + this._ready = true; + this._readyEvent.raiseEvent(this); + // This is here for backwards compatibility and can be removed when readyPromise is removed. + this._completeLoad = this._completeLoad && this._completeLoad(); + if (!this._loader.incrementallyLoadTextures) { + this._texturesLoaded = true; + this._texturesReadyEvent.raiseEvent(this); + this._completeTexturesLoad = + this._completeTexturesLoad && this._completeTexturesLoad(); + } + }); // Don't render until the next frame after the ready promise is resolved return; } + if (this._loader.incrementallyLoadTextures && !this._texturesLoaded) { + // TODO: Check if textures are actually ready + // Re-run the pipeline so texture memory statistics are re-computed + this.resetDrawCommands(); + + this._texturesLoaded = true; + this._texturesReadyEvent.raiseEvent(this); + this._completeTexturesLoad = + this._completeTexturesLoad && this._completeTexturesLoad(); + } + updatePickIds(this); // Update the scene graph and draw commands for any changes in model's properties @@ -1825,8 +1947,12 @@ Model.prototype.update = function (frameState) { function processLoader(model, frameState) { if (!model._resourcesLoaded || !model._texturesLoaded) { - model._loader.process(frameState); + // Ensures frames continue to render in requestRender mode while resources are processing + frameState.afterRender.push(() => true); + return model._loader.process(frameState); } + + return true; } function updateCustomShader(model, frameState) { @@ -2635,6 +2761,11 @@ Model.prototype.destroyModelResources = function () { * @returns {Model} The newly created model. */ Model.fromGltf = function (options) { + deprecationWarning( + "Model.fromGltf", + "Model.fromGltf was deprecated in CesiumJS 1.102. It will be removed in 1.104. Use Model.fromGltfAsync instead." + ); + options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); @@ -2688,6 +2819,179 @@ Model.fromGltf = function (options) { return model; }; +/** + *

+ * Asynchronously creates a model from a glTF asset. This function returns a promise that resolves when the model is ready to render, i.e., when the external binary, image, + * and shader files are downloaded and the WebGL resources are created. + *

+ *

+ * The model can be a traditional glTF asset with a .gltf extension or a Binary glTF using the .glb extension. + * + * @param {Object} options Object with the following properties: + * @param {String|Resource} options.url The url to the .gltf or .glb file. + * @param {String|Resource} [options.basePath=''] The base path that paths in the glTF JSON are relative to. + * @param {Boolean} [options.show=true] Whether or not to render the model. + * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates. + * @param {Number} [options.scale=1.0] A uniform scale applied to this model. + * @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom. + * @param {Number} [options.maximumScale] The maximum scale size of a model. An upper limit for minimumPixelSize. + * @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}. + * @param {Boolean} [options.allowPicking=true] When true, each primitive is pickable with {@link Scene#pick}. + * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded. + * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. + * @param {Boolean} [options.clampAnimations=true] Determines if the model's animations should hold a pose over frames where no keyframes are specified. + * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from light sources. + * @param {Boolean} [options.releaseGltfJson=false] When true, the glTF JSON is released once the glTF is loaded. This is is especially useful for cases like 3D Tiles, where each .gltf model is unique and caching the glTF JSON is not effective. + * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model. + * @param {Boolean} [options.enableDebugWireframe=false] For debugging only. This must be set to true for debugWireframe to work in WebGL1. This cannot be set after the model has loaded. + * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe. Will only work for WebGL1 if enableDebugWireframe is set to true. + * @param {Boolean} [options.cull=true] Whether or not to cull the model using frustum/horizon culling. If the model is part of a 3D Tiles tileset, this property will always be false, since the 3D Tiles culling system is used. + * @param {Boolean} [options.opaquePass=Pass.OPAQUE] The pass to use in the {@link DrawCommand} for the opaque portions of the model. + * @param {Axis} [options.upAxis=Axis.Y] The up-axis of the glTF model. + * @param {Axis} [options.forwardAxis=Axis.Z] The forward-axis of the glTF model. + * @param {CustomShader} [options.customShader] A custom shader. This will add user-defined GLSL code to the vertex and fragment shaders. Using custom shaders with a {@link Cesium3DTileStyle} may lead to undefined behavior. + * @param {Cesium3DTileContent} [options.content] The tile content this model belongs to. This property will be undefined if model is not loaded as part of a tileset. + * @param {HeightReference} [options.heightReference=HeightReference.NONE] Determines how the model is drawn relative to terrain. + * @param {Scene} [options.scene] Must be passed in for models that use the height reference property. + * @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this model will be displayed. + * @param {Color} [options.color] A color that blends with the model's rendered color. + * @param {ColorBlendMode} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] Defines how the color blends with the model. + * @param {Number} [options.colorBlendAmount=0.5] Value used to determine the color strength when the colorBlendMode is MIX. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two. + * @param {Color} [options.silhouetteColor=Color.RED] The silhouette color. If more than 256 models have silhouettes enabled, there is a small chance that overlapping models will have minor artifacts. + * @param {Number} [options.silhouetteSize=0.0] The size of the silhouette in pixels. + * @param {Boolean} [options.enableShowOutline=true] Whether to enable outlines for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. This can be set false to avoid post-processing geometry at load time. When false, the showOutlines and outlineColor options are ignored. + * @param {Boolean} [options.showOutline=true] Whether to display the outline for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. When true, outlines are displayed. When false, outlines are not displayed. + * @param {Color} [options.outlineColor=Color.BLACK] The color to use when rendering outlines. + * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model. + * @param {Cartesian3} [options.lightColor] The light color when shading the model. When undefined the scene's light color is used instead. + * @param {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting on this model. + * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the material's doubleSided property; when false, back face culling is disabled. Back faces are not culled if the model's color is translucent. + * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas. + * @param {Boolean} [options.showCreditsOnScreen=false] Whether to display the credits of this model on screen. + * @param {SplitDirection} [options.splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this model. + * @param {Boolean} [options.projectTo2D=false] Whether to accurately project the model's positions in 2D. If this is true, the model will be projected accurately to 2D, but it will use more memory to do so. If this is false, the model will use less memory and will still render in 2D / CV mode, but its positions may be inaccurate. This disables minimumPixelSize and prevents future modification to the model matrix. This also cannot be set after the model has loaded. + * @param {String|Number} [options.featureIdLabel="featureId_0"] Label of the feature ID set to use for picking and styling. For EXT_mesh_features, this is the feature ID's label property, or "featureId_N" (where N is the index in the featureIds array) when not specified. EXT_feature_metadata did not have a label field, so such feature ID sets are always labeled "featureId_N" where N is the index in the list of all feature Ids, where feature ID attributes are listed before feature ID textures. If featureIdLabel is an integer N, it is converted to the string "featureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. + * @param {String|Number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. + * @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation and lighting. + * @param {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this model. This cannot be set after the model has loaded. + * + * @returns {Promise} A promise that resolves to the created model when it is ready to render. + * + * @exception {RuntimeError} The model failed to load. + * + * @example + * // Load a model and add it to the scene + * try { + * const model = await Cesium.Model.fromGltfAsync({ + * url: "../../SampleData/models/CesiumMan/Cesium_Man.glb" + * }); + * viewer.scene.primitives.add(model); + * } catch (error) { + * console.log(`Failed to load model. ${error}`); + * } + * + * @example + * // Position a model with modelMatrix and display it with a minimum size of 128 pixels + * const position = Cesium.Cartesian3.fromDegrees( + * -123.0744619, + * 44.0503706, + * 5000.0 + * ); + * const headingPositionRoll = new Cesium.HeadingPitchRoll(); + * const fixedFrameTransform = Cesium.Transforms.localFrameToFixedFrameGenerator( + * "north", + * "west" + * ); + * try { + * const model = await Cesium.Model.fromGltfAsync({ + * url: "../../SampleData/models/CesiumAir/Cesium_Air.glb", + * modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame( + * position, + * headingPositionRoll, + * Cesium.Ellipsoid.WGS84, + * fixedFrameTransform + * ), + * minimumPixelSize: 128, + * }); + * viewer.scene.primitives.add(model); + * } catch (error) { + * console.log(`Failed to load model. ${error}`); + * } + */ +Model.fromGltfAsync = async function (options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + //>>includeStart('debug', pragmas.debug); + if (!defined(options.url) && !defined(options.gltf)) { + throw new DeveloperError("options.url is required."); + } + //>>includeEnd('debug'); + + // options.gltf is used internally for 3D Tiles. It can be a Resource, a URL + // to a glTF/glb file, a binary glTF buffer, or a JSON object containing the + // glTF contents. + const gltf = defaultValue(options.url, options.gltf); + + const loaderOptions = { + releaseGltfJson: options.releaseGltfJson, + asynchronous: options.asynchronous, + incrementallyLoadTextures: options.incrementallyLoadTextures, + upAxis: options.upAxis, + forwardAxis: options.forwardAxis, + loadAttributesFor2D: options.projectTo2D, + loadIndicesForWireframe: options.enableDebugWireframe, + loadPrimitiveOutline: options.enableShowOutline, + loadForClassification: defined(options.classificationType), + }; + + const basePath = defaultValue(options.basePath, ""); + const baseResource = Resource.createIfNeeded(basePath); + + if (defined(gltf.asset)) { + loaderOptions.gltfJson = gltf; + loaderOptions.baseResource = baseResource; + loaderOptions.gltfResource = baseResource; + } else if (gltf instanceof Uint8Array) { + loaderOptions.typedArray = gltf; + loaderOptions.baseResource = baseResource; + loaderOptions.gltfResource = baseResource; + } else { + loaderOptions.gltfResource = Resource.createIfNeeded(gltf); + } + + const loader = new GltfLoader(loaderOptions); + + const is3DTiles = defined(options.content); + const type = is3DTiles ? ModelType.TILE_GLTF : ModelType.GLTF; + + const resource = loaderOptions.gltfResource; + + const modelOptions = makeModelOptions(loader, type, options); + modelOptions.resource = resource; + + try { + // This load the gltf JSON and ensures the gltf is valid + // Further resource loading is handled synchronously in loader.process(), and requires + // hooking into model's update() as the frameState is needed + await loader.load(); + + const model = new Model(modelOptions); + + const resourceCredits = model._resource.credits; + if (defined(resourceCredits)) { + const length = resourceCredits.length; + for (let i = 0; i < length; i++) { + model._resourceCredits.push(resourceCredits[i]); + } + } + + return model; + } catch (error) { + loader.destroy(); + throw ModelUtility.getError("model", resource, error); + } +}; + /* * @private */ diff --git a/packages/engine/Source/Scene/Model/Model3DTileContent.js b/packages/engine/Source/Scene/Model/Model3DTileContent.js index 54c98c4b4aad..51744b06bf2a 100644 --- a/packages/engine/Source/Scene/Model/Model3DTileContent.js +++ b/packages/engine/Source/Scene/Model/Model3DTileContent.js @@ -276,6 +276,7 @@ Model3DTileContent.fromGltf = function (tileset, tile, resource, gltf) { modelOptions.classificationType = classificationType; + // This should be removed when readyPromise is deprecated across 3D Tiles functions const model = Model.fromGltf(modelOptions); content._model = model; // Include the animation setup in the ready promise to avoid an uncaught exception @@ -319,13 +320,8 @@ Model3DTileContent.fromB3dm = function ( const model = Model.fromB3dm(modelOptions); content._model = model; - // Include the animation setup in the ready promise to avoid an uncaught exception - content._readyPromise = model.readyPromise.then(function (model) { - model.activeAnimations.addAll({ - loop: ModelAnimationLoop.REPEAT, - }); - return model; - }); + // This should be removed when readyPromise is deprecated across 3D Tiles functions + content._readyPromise = model._readyPromise; return content; }; @@ -354,13 +350,8 @@ Model3DTileContent.fromI3dm = function ( const model = Model.fromI3dm(modelOptions); content._model = model; - // Include the animation setup in the ready promise to avoid an uncaught exception - content._readyPromise = model.readyPromise.then(function (model) { - model.activeAnimations.addAll({ - loop: ModelAnimationLoop.REPEAT, - }); - return model; - }); + // This should be removed when readyPromise is deprecated across 3D Tiles functions + content._readyPromise = model._readyPromise; return content; }; @@ -388,7 +379,8 @@ Model3DTileContent.fromPnts = function ( ); const model = Model.fromPnts(modelOptions); content._model = model; - content._readyPromise = model.readyPromise; + // This should be removed when readyPromise is deprecated across 3D Tiles functions + content._readyPromise = model._readyPromise; return content; }; @@ -409,7 +401,8 @@ Model3DTileContent.fromGeoJson = function (tileset, tile, resource, geoJson) { ); const model = Model.fromGeoJson(modelOptions); content._model = model; - content._readyPromise = model.readyPromise; + // This should be removed when readyPromise is deprecated across 3D Tiles functions + content._readyPromise = model._readyPromise; return content; }; diff --git a/packages/engine/Source/Scene/Model/ModelAnimationCollection.js b/packages/engine/Source/Scene/Model/ModelAnimationCollection.js index 8c1c0eb76972..8cd93901fed5 100644 --- a/packages/engine/Source/Scene/Model/ModelAnimationCollection.js +++ b/packages/engine/Source/Scene/Model/ModelAnimationCollection.js @@ -174,7 +174,7 @@ ModelAnimationCollection.prototype.add = function (options) { //>>includeStart('debug', pragmas.debug); if (!model.ready) { throw new DeveloperError( - "Animations are not loaded. Wait for Model.readyPromise to resolve." + "Animations are not loaded. Wait for Model.ready to be true." ); } //>>includeEnd('debug'); @@ -258,7 +258,7 @@ ModelAnimationCollection.prototype.addAll = function (options) { //>>includeStart('debug', pragmas.debug); if (!model.ready) { throw new DeveloperError( - "Animations are not loaded. Wait for Model.readyPromise to resolve." + "Animations are not loaded. Wait for Model.ready to be true." ); } diff --git a/packages/engine/Source/Scene/Model/ModelUtility.js b/packages/engine/Source/Scene/Model/ModelUtility.js index b99f9e8076bd..f9f0c052b65c 100644 --- a/packages/engine/Source/Scene/Model/ModelUtility.js +++ b/packages/engine/Source/Scene/Model/ModelUtility.js @@ -20,29 +20,27 @@ function ModelUtility() {} /** * Create a function for reporting when a model fails to load * - * @param {Model} model The model to report about * @param {String} type The type of object to report about * @param {String} path The URI of the model file - * @returns {Function} An error function that throws an error for the failed model + * @param {Error} [error] The error which caused the failure + * @returns {RuntimeError} An error for the failed model * * @private */ -ModelUtility.getFailedLoadFunction = function (model, type, path) { - return function (error) { - let message = `Failed to load ${type}: ${path}`; - if (defined(error)) { - message += `\n${error.message}`; - } +ModelUtility.getError = function (type, path, error) { + let message = `Failed to load ${type}: ${path}`; + if (defined(error) && defined(error.message)) { + message += `\n${error.message}`; + } - const runtimeError = new RuntimeError(message); - if (defined(error)) { - // the original call stack is often more useful than the new error's stack, - // so add the information here - runtimeError.stack = `Original stack:\n${error.stack}\nHandler stack:\n${runtimeError.stack}`; - } + const runtimeError = new RuntimeError(message); + if (defined(error)) { + // the original call stack is often more useful than the new error's stack, + // so add the information here + runtimeError.stack = `Original stack:\n${error.stack}\nHandler stack:\n${runtimeError.stack}`; + } - return Promise.reject(runtimeError); - }; + return runtimeError; }; /** @@ -372,6 +370,8 @@ ModelUtility.supportedExtensions = { * a {@link RuntimeError} with the extension name. * * @param {Array} extensionsRequired The extensionsRequired array in the glTF. + * + * @exception {RuntimeError} Unsupported glTF Extension */ ModelUtility.checkSupportedExtensions = function (extensionsRequired) { const length = extensionsRequired.length; diff --git a/packages/engine/Source/Scene/ResourceCache.js b/packages/engine/Source/Scene/ResourceCache.js index 862b39aeeaa1..649e34294d01 100644 --- a/packages/engine/Source/Scene/ResourceCache.js +++ b/packages/engine/Source/Scene/ResourceCache.js @@ -69,20 +69,17 @@ ResourceCache.get = function (cacheKey) { }; /** - * Loads a resource and adds it to the cache. + * Adds it to the cache. * - * @param {Object} options Object with the following properties: - * @param {ResourceLoader} options.resourceLoader The resource. + * @param {ResourceLoader} resourceLoader The resource. + * @returns {ResourceLoader} The resource. * - * @exception {DeveloperError} Resource with this cacheKey is already in the cach + * @exception {DeveloperError} Resource with this cacheKey is already in the cache * @private */ -ResourceCache.load = function (options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - const resourceLoader = options.resourceLoader; - +ResourceCache.add = function (resourceLoader) { //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("options.resourceLoader", resourceLoader); + Check.typeOf.object("resourceLoader", resourceLoader); //>>includeEnd('debug'); const cacheKey = resourceLoader.cacheKey; @@ -99,7 +96,7 @@ ResourceCache.load = function (options) { ResourceCache.cacheEntries[cacheKey] = new CacheEntry(resourceLoader); - resourceLoader.load(); + return resourceLoader; }; /** @@ -136,18 +133,18 @@ ResourceCache.unload = function (resourceLoader) { }; /** - * Loads a schema from the cache. + * Gets an existing schema loader from the cache, or creates a new loader if one does not already exist. * * @param {Object} options Object with the following properties: * @param {Object} [options.schema] An object that explicitly defines a schema JSON. Mutually exclusive with options.resource. * @param {Resource} [options.resource] The {@link Resource} pointing to the schema JSON. Mutually exclusive with options.schema. * - * @returns {MetadataSchemaLoader} The schema resource. + * @returns {MetadataSchemaLoader} The cached schema resource. * * @exception {DeveloperError} One of options.schema and options.resource must be defined. * @private */ -ResourceCache.loadSchema = function (options) { +ResourceCache.getSchemaLoader = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const schema = options.schema; const resource = options.resource; @@ -176,25 +173,21 @@ ResourceCache.loadSchema = function (options) { cacheKey: cacheKey, }); - ResourceCache.load({ - resourceLoader: schemaLoader, - }); - - return schemaLoader; + return ResourceCache.add(schemaLoader); }; /** - * Load an embedded buffer from the cache. + * Gets an existing embedded buffer loader from the cache, or creates a new loader if one does not already exist. * * @param {Object} options Object with the following properties: * @param {Resource} options.parentResource The {@link Resource} containing the embedded buffer. * @param {Number} options.bufferId A unique identifier of the embedded buffer within the parent resource. * @param {Uint8Array} [options.typedArray] The typed array containing the embedded buffer contents. * - * @returns {BufferLoader} The buffer loader. + * @returns {BufferLoader} The cached buffer loader. * @private */ -ResourceCache.loadEmbeddedBuffer = function (options) { +ResourceCache.getEmbeddedBufferLoader = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const parentResource = options.parentResource; const bufferId = options.bufferId; @@ -224,23 +217,19 @@ ResourceCache.loadEmbeddedBuffer = function (options) { cacheKey: cacheKey, }); - ResourceCache.load({ - resourceLoader: bufferLoader, - }); - - return bufferLoader; + return ResourceCache.add(bufferLoader); }; /** - * Loads an external buffer from the cache. + * Gets an existing external buffer from loader the cache, or creates a new loader if one does not already exist. * * @param {Object} options Object with the following properties: * @param {Resource} options.resource The {@link Resource} pointing to the external buffer. * - * @returns {BufferLoader} The buffer loader. + * @returns {BufferLoader} The cached buffer loader. * @private */ -ResourceCache.loadExternalBuffer = function (options) { +ResourceCache.getExternalBufferLoader = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const resource = options.resource; @@ -262,15 +251,11 @@ ResourceCache.loadExternalBuffer = function (options) { cacheKey: cacheKey, }); - ResourceCache.load({ - resourceLoader: bufferLoader, - }); - - return bufferLoader; + return ResourceCache.add(bufferLoader); }; /** - * Loads a glTF JSON from the cache. + * Gets an existing glTF JSON loader from the cache, or creates a new loader if one does not already exist. * * @param {Object} options Object with the following properties: * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. @@ -278,10 +263,10 @@ ResourceCache.loadExternalBuffer = function (options) { * @param {Uint8Array} [options.typedArray] The typed array containing the glTF contents. * @param {Object} [options.gltfJson] The parsed glTF JSON contents. * - * @returns {GltfJsonLoader} The glTF JSON. + * @returns {GltfJsonLoader} The cached glTF JSON loader. * @private */ -ResourceCache.loadGltfJson = function (options) { +ResourceCache.getGltfJsonLoader = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltfResource = options.gltfResource; const baseResource = options.baseResource; @@ -311,15 +296,11 @@ ResourceCache.loadGltfJson = function (options) { cacheKey: cacheKey, }); - ResourceCache.load({ - resourceLoader: gltfJsonLoader, - }); - - return gltfJsonLoader; + return ResourceCache.add(gltfJsonLoader); }; /** - * Loads a glTF buffer view from the cache. + * Gets an existing glTF buffer view from the cache, or creates a new loader if one does not already exist. * * @param {Object} options Object with the following properties: * @param {Object} options.gltf The glTF JSON. @@ -327,10 +308,10 @@ ResourceCache.loadGltfJson = function (options) { * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * - * @returns {GltfBufferViewLoader} The buffer view loader. + * @returns {GltfBufferViewLoader>} The cached buffer view loader. * @private */ -ResourceCache.loadBufferView = function (options) { +ResourceCache.getBufferViewLoader = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const bufferViewId = options.bufferViewId; @@ -365,15 +346,11 @@ ResourceCache.loadBufferView = function (options) { cacheKey: cacheKey, }); - ResourceCache.load({ - resourceLoader: bufferViewLoader, - }); - - return bufferViewLoader; + return ResourceCache.add(bufferViewLoader); }; /** - * Loads Draco data from the cache. + * Gets an existing Draco data from the cache, or creates a new loader if one does not already exist. * * @param {Object} options Object with the following properties: * @param {Object} options.gltf The glTF JSON. @@ -381,10 +358,10 @@ ResourceCache.loadBufferView = function (options) { * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * - * @returns {GltfDracoLoader} The Draco loader. + * @returns {GltfDracoLoader} The cached Draco loader. * @private */ -ResourceCache.loadDraco = function (options) { +ResourceCache.getDracoLoader = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const draco = options.draco; @@ -419,15 +396,11 @@ ResourceCache.loadDraco = function (options) { cacheKey: cacheKey, }); - ResourceCache.load({ - resourceLoader: dracoLoader, - }); - - return dracoLoader; + return ResourceCache.add(dracoLoader); }; /** - * Loads a glTF vertex buffer from the cache. + * Gets an existing glTF vertex buffer from the cache, or creates a new loader if one does not already exist. * * @param {Object} options Object with the following properties: * @param {Object} options.gltf The glTF JSON. @@ -446,10 +419,10 @@ ResourceCache.loadDraco = function (options) { * @exception {DeveloperError} When options.draco is defined options.attributeSemantic must also be defined. * @exception {DeveloperError} When options.draco is defined options.accessorId must also be defined. * - * @returns {GltfVertexBufferLoader} The vertex buffer loader. + * @returns {GltfVertexBufferLoader} The cached vertex buffer loader. * @private */ -ResourceCache.loadVertexBuffer = function (options) { +ResourceCache.getVertexBufferLoader = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const gltfResource = options.gltfResource; @@ -539,18 +512,7 @@ ResourceCache.loadVertexBuffer = function (options) { loadTypedArray: loadTypedArray, }); - ResourceCache.load({ - resourceLoader: vertexBufferLoader, - }); - - const promise = ResourceCache.statistics.addGeometryLoader( - vertexBufferLoader - ); - - // Needed for unit testing - ResourceCache.cacheEntries[cacheKey]._statisticsPromise = promise; - - return vertexBufferLoader; + return ResourceCache.add(vertexBufferLoader); }; function hasDracoCompression(draco, semantic) { @@ -562,7 +524,7 @@ function hasDracoCompression(draco, semantic) { } /** - * Loads a glTF index buffer from the cache. + * Gets an existing glTF index buffer from the cache, or creates a new loader if one does not already exist. * * @param {Object} options Object with the following properties: * @param {Object} options.gltf The glTF JSON. @@ -574,10 +536,10 @@ function hasDracoCompression(draco, semantic) { * @param {Boolean} [options.asynchronous=true] Determines if WebGL resource creation will be spread out over several frames or block until all WebGL resources are created. * @param {Boolean} [options.loadBuffer=false] Load index buffer as a GPU index buffer. * @param {Boolean} [options.loadTypedArray=false] Load index buffer as a typed array. - * @returns {GltfIndexBufferLoader} The index buffer loader. + * @returns {GltfIndexBufferLoader} The cached index buffer loader. * @private */ -ResourceCache.loadIndexBuffer = function (options) { +ResourceCache.getIndexBufferLoader = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const accessorId = options.accessorId; @@ -631,19 +593,11 @@ ResourceCache.loadIndexBuffer = function (options) { loadTypedArray: loadTypedArray, }); - ResourceCache.load({ - resourceLoader: indexBufferLoader, - }); - const promise = ResourceCache.statistics.addGeometryLoader(indexBufferLoader); - - // Needed for unit testing - ResourceCache.cacheEntries[cacheKey]._statisticsPromise = promise; - - return indexBufferLoader; + return ResourceCache.add(indexBufferLoader); }; /** - * Loads a glTF image from the cache. + * Gets an existing glTF image from the cache, or creates a new loader if one does not already exist. * * @param {Object} options Object with the following properties: * @param {Object} options.gltf The glTF JSON. @@ -651,10 +605,10 @@ ResourceCache.loadIndexBuffer = function (options) { * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * - * @returns {GltfImageLoader} The image loader. + * @returns {GltfImageLoader} The cached image loader. * @private */ -ResourceCache.loadImage = function (options) { +ResourceCache.getImageLoader = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const imageId = options.imageId; @@ -689,15 +643,11 @@ ResourceCache.loadImage = function (options) { cacheKey: cacheKey, }); - ResourceCache.load({ - resourceLoader: imageLoader, - }); - - return imageLoader; + return ResourceCache.add(imageLoader); }; /** - * Loads a glTF texture from the cache. + * Gets an existing glTF texture from the cache, or creates a new loader if one does not already exist. * * @param {Object} options Object with the following properties: * @param {Object} options.gltf The glTF JSON. @@ -708,10 +658,10 @@ ResourceCache.loadImage = function (options) { * @param {FrameState} options.frameState The frame state. * @param {Boolean} [options.asynchronous=true] Determines if WebGL resource creation will be spread out over several frames or block until all WebGL resources are created. * - * @returns {GltfTextureLoader} The texture loader. + * @returns {GltfTextureLoader} The cached texture loader. * @private */ -ResourceCache.loadTexture = function (options) { +ResourceCache.getTextureLoader = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const textureInfo = options.textureInfo; @@ -755,15 +705,7 @@ ResourceCache.loadTexture = function (options) { asynchronous: asynchronous, }); - ResourceCache.load({ - resourceLoader: textureLoader, - }); - const promise = ResourceCache.statistics.addTextureLoader(textureLoader); - - // Needed for unit testing - ResourceCache.cacheEntries[cacheKey]._statisticsPromise = promise; - - return textureLoader; + return ResourceCache.add(textureLoader); }; /** diff --git a/packages/engine/Source/Scene/ResourceCacheStatistics.js b/packages/engine/Source/Scene/ResourceCacheStatistics.js index 70fef98fbc39..cdc954298a81 100644 --- a/packages/engine/Source/Scene/ResourceCacheStatistics.js +++ b/packages/engine/Source/Scene/ResourceCacheStatistics.js @@ -47,14 +47,12 @@ ResourceCacheStatistics.prototype.clear = function () { }; /** - * Track the resources for a vertex or index buffer loader. This is implemented - * asynchronously since resources may not be immediately available to count. + * Track the resources for a vertex or index buffer loader. This should be called after a loader is ready; that + * is it has been loaded and processed. * This method handles the following cases gracefully: *

    *
  • If the loader is added twice, its resources will not be double-counted
  • *
  • If the geometry has a CPU copy of the GPU buffer, it will be added to the count
  • - *
  • If the resource loading failed, its resources will not be counted
  • - *
  • If removeLoader() was called before the loader promise resolves, its resources will not be counted
  • *
* @param {GltfVertexBufferLoader|GltfIndexBufferLoader} loader The geometry buffer with resources to track * @returns {Promise} A promise that resolves once the count is updated. @@ -75,51 +73,33 @@ ResourceCacheStatistics.prototype.addGeometryLoader = function (loader) { this._geometrySizes[cacheKey] = 0; - const that = this; - return loader.promise - .then(function (loader) { - // loader was unloaded before its promise resolved - if (!that._geometrySizes.hasOwnProperty(cacheKey)) { - return; - } - - const buffer = loader.buffer; - const typedArray = loader.typedArray; - - let totalSize = 0; - - if (defined(buffer)) { - totalSize += buffer.sizeInBytes; - } - - if (defined(typedArray)) { - totalSize += typedArray.byteLength; - } - - that.geometryByteLength += totalSize; - that._geometrySizes[cacheKey] = totalSize; - }) - .catch(function () { - // If the resource failed to load, remove it from the cache - delete that._geometrySizes[cacheKey]; - }); + const buffer = loader.buffer; + const typedArray = loader.typedArray; + + let totalSize = 0; + + if (defined(buffer)) { + totalSize += buffer.sizeInBytes; + } + + if (defined(typedArray)) { + totalSize += typedArray.byteLength; + } + + this.geometryByteLength += totalSize; + this._geometrySizes[cacheKey] = totalSize; }; /** - * Track the resources for a texture loader. This is implemented - * asynchronously since resources may not be immediately available to count. - * This method handles the following cases gracefully: - *
    - *
  • If the loader is added twice, its resources will not be double-counted
  • - *
  • If the resource loading failed, its resources will not be counted
  • - *
  • If removeLoader() was called before the loader promise resolves, its resources will not be counted
  • - *
+ * Track the resources for a texture loader. This should be called after a loader is ready; that + * is it has been loaded and processed. + * If the loader is added twice, its resources will not be double-counted. + * * @param {GltfTextureLoader} loader The texture loader with resources to track - * @returns {Promise} A promise that resolves once the count is updated. * * @private */ -ResourceCacheStatistics.prototype.addTextureLoader = function (loader) { +ResourceCacheStatistics.prototype.addTextureLoader = async function (loader) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("loader", loader); //>>includeEnd('debug'); @@ -132,22 +112,9 @@ ResourceCacheStatistics.prototype.addTextureLoader = function (loader) { } this._textureSizes[cacheKey] = 0; - - const that = this; - return loader.promise - .then(function (loader) { - // loader was unloaded before its promise resolved - if (!that._textureSizes.hasOwnProperty(cacheKey)) { - return; - } - - const totalSize = loader.texture.sizeInBytes; - that.texturesByteLength += loader.texture.sizeInBytes; - that._textureSizes[cacheKey] = totalSize; - }) - .catch(function () { - delete that._textureSizes[cacheKey]; - }); + const totalSize = loader.texture.sizeInBytes; + this.texturesByteLength += loader.texture.sizeInBytes; + this._textureSizes[cacheKey] = totalSize; }; /** diff --git a/packages/engine/Source/Scene/ResourceLoader.js b/packages/engine/Source/Scene/ResourceLoader.js index aa5756207e6e..e3ebe08ef0cc 100644 --- a/packages/engine/Source/Scene/ResourceLoader.js +++ b/packages/engine/Source/Scene/ResourceLoader.js @@ -20,21 +20,6 @@ import RuntimeError from "../Core/RuntimeError.js"; function ResourceLoader() {} Object.defineProperties(ResourceLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. - * - * @memberof ResourceLoader.prototype - * - * @type {Promise.|undefined} - * @readonly - * @private - */ - promise: { - // eslint-disable-next-line getter-return - get: function () { - DeveloperError.throwInstantiationError(); - }, - }, /** * The cache key of the resource. * @@ -71,9 +56,12 @@ ResourceLoader.prototype.unload = function () {}; * Processes the resource until it becomes ready. * * @param {FrameState} frameState The frame state. + * @returns {boolean} true once all resourced are ready. * @private */ -ResourceLoader.prototype.process = function (frameState) {}; +ResourceLoader.prototype.process = function (frameState) { + return false; +}; /** * Constructs a {@link RuntimeError} from an errorMessage and an error. @@ -89,7 +77,7 @@ ResourceLoader.prototype.getError = function (errorMessage, error) { Check.typeOf.string("errorMessage", errorMessage); //>>includeEnd('debug'); - if (defined(error)) { + if (defined(error) && defined(error.message)) { errorMessage += `\n${error.message}`; } diff --git a/packages/engine/Source/Scene/ResourceLoaderState.js b/packages/engine/Source/Scene/ResourceLoaderState.js index df206e2943f1..f48c10fac2d1 100644 --- a/packages/engine/Source/Scene/ResourceLoaderState.js +++ b/packages/engine/Source/Scene/ResourceLoaderState.js @@ -21,13 +21,21 @@ const ResourceLoaderState = { */ LOADING: 1, /** - * The resource has finished loading, but requires further processing. GPU resources are allocated in this state as needed. + * The resource has finished loading, but requires further processing. * * @type {Number} * @constant * @private */ - PROCESSING: 2, + LOADED: 2, + /** + * The resource is processing. GPU resources are allocated in this state as needed. + * + * @type {Number} + * @constant + * @private + */ + PROCESSING: 3, /** * The resource has finished loading and processing; the results are ready to be used. * @@ -35,7 +43,7 @@ const ResourceLoaderState = { * @constant * @private */ - READY: 3, + READY: 4, /** * The resource loading or processing has failed due to an error. * @@ -43,6 +51,6 @@ const ResourceLoaderState = { * @constant * @private */ - FAILED: 4, + FAILED: 5, }; export default Object.freeze(ResourceLoaderState); diff --git a/packages/engine/Specs/DataSources/ModelVisualizerSpec.js b/packages/engine/Specs/DataSources/ModelVisualizerSpec.js index 27c8c3181f57..3f3325dd3482 100644 --- a/packages/engine/Specs/DataSources/ModelVisualizerSpec.js +++ b/packages/engine/Specs/DataSources/ModelVisualizerSpec.js @@ -3,6 +3,7 @@ import { Cartesian2, Cartesian3, Color, + defined, DistanceDisplayCondition, HeightReference, JulianDate, @@ -58,25 +59,25 @@ describe( scene.destroyForSpecs(); }); - it("constructor throws if no scene is passed.", function () { + it("constructor throws if no scene is passed", function () { expect(function () { return new ModelVisualizer(undefined, entityCollection); }).toThrowDeveloperError(); }); - it("constructor throws if no entityCollection is passed.", function () { + it("constructor throws if no entityCollection is passed", function () { expect(function () { return new ModelVisualizer(scene, undefined); }).toThrowDeveloperError(); }); - it("update throws if no time specified.", function () { + it("update throws if no time specified", function () { expect(function () { visualizer.update(); }).toThrowDeveloperError(); }); - it("isDestroy returns false until destroyed.", function () { + it("isDestroy returns false until destroyed", function () { expect(visualizer.isDestroyed()).toEqual(false); visualizer.destroy(); expect(visualizer.isDestroyed()).toEqual(true); @@ -90,7 +91,7 @@ describe( visualizer = undefined; }); - it("object with no model does not create one.", function () { + it("object with no model does not create one", function () { const testObject = entityCollection.getOrCreateEntity("test"); testObject.position = new ConstantProperty( new Cartesian3(1234, 5678, 9101112) @@ -99,7 +100,7 @@ describe( expect(scene.primitives.length).toEqual(0); }); - it("object with no position does not create a model.", function () { + it("object with no position does not create a model", function () { const testObject = entityCollection.getOrCreateEntity("test"); const model = (testObject.model = new ModelGraphics()); model.uri = new ConstantProperty(boxUrl); @@ -108,7 +109,7 @@ describe( expect(scene.primitives.length).toEqual(0); }); - it("A ModelGraphics causes a primitive to be created and updated.", function () { + it("creates and updates a primitive from ModelGraphics", async function () { const time = JulianDate.now(); const model = new ModelGraphics(); @@ -153,10 +154,14 @@ describe( visualizer.update(time); - expect(scene.primitives.length).toEqual(1); + let primitive; + await pollToPromise(function () { + primitive = scene.primitives.get(0); + return defined(primitive); + }); - const primitive = scene.primitives.get(0); visualizer.update(time); + expect(primitive.show).toEqual(true); expect(primitive.scale).toEqual(2); expect(primitive.minimumPixelSize).toEqual(24.0); @@ -187,35 +192,36 @@ describe( expect(primitive.imageBasedLighting.imageBasedLightingFactor).toEqual( new Cartesian2(0.5, 0.5) ); + expect(primitive.lightColor).toEqual(new Color(1.0, 1.0, 0.0, 1.0)); // wait till the model is loaded before we can check node transformations - return pollToPromise(function () { + await pollToPromise(function () { scene.render(); return primitive.ready; - }).then(function () { - visualizer.update(time); + }); - const node = primitive.getNode("Root"); - expect(node).toBeDefined(); + visualizer.update(time); - const transformationMatrix = Matrix4.fromTranslationQuaternionRotationScale( - translation, - rotation, - scale - ); + const node = primitive.getNode("Root"); + expect(node).toBeDefined(); - Matrix4.multiplyTransformation( - node.originalMatrix, - transformationMatrix, - transformationMatrix - ); + const transformationMatrix = Matrix4.fromTranslationQuaternionRotationScale( + translation, + rotation, + scale + ); - expect(node.matrix).toEqual(transformationMatrix); - }); + Matrix4.multiplyTransformation( + node.originalMatrix, + transformationMatrix, + transformationMatrix + ); + + expect(node.matrix).toEqual(transformationMatrix); }); - it("can apply model articulations", function () { + it("can apply model articulations", async function () { const time = JulianDate.now(); const model = new ModelGraphics(); @@ -243,43 +249,44 @@ describe( visualizer.update(time); - expect(scene.primitives.length).toEqual(1); - - const primitive = scene.primitives.get(0); + let primitive; + await pollToPromise(function () { + primitive = scene.primitives.get(0); + return defined(primitive); + }); // wait till the model is loaded before we can check articulations - return pollToPromise(function () { + await pollToPromise(function () { scene.render(); return primitive.ready; - }).then(function () { - visualizer.update(time); - - const node = primitive.getNode("Root"); - - const expected = [ - 0.7147690483240505, - -0.04340611926232735, - -0.0749741046529782, - 0, - -0.06188330295778636, - 0.05906797312763484, - -0.6241645867602773, - 0, - 0.03752515582279579, - 0.5366347296529127, - 0.04706410108373541, - 0, - 1, - 3, - -2, - 1, - ]; - - expect(node.matrix).toEqualEpsilon(expected, CesiumMath.EPSILON14); }); + visualizer.update(time); + + const node = primitive.getNode("Root"); + + const expected = [ + 0.7147690483240505, + -0.04340611926232735, + -0.0749741046529782, + 0, + -0.06188330295778636, + 0.05906797312763484, + -0.6241645867602773, + 0, + 0.03752515582279579, + 0.5366347296529127, + 0.04706410108373541, + 0, + 1, + 3, + -2, + 1, + ]; + + expect(node.matrix).toEqualEpsilon(expected, CesiumMath.EPSILON14); }); - it("A ModelGraphics with a Resource causes a primitive to be created.", function () { + it("creates a primitive from ModelGraphics with a Resource", async function () { const time = JulianDate.now(); const model = new ModelGraphics(); @@ -298,23 +305,25 @@ describe( visualizer.update(time); - expect(scene.primitives.length).toEqual(1); - - const primitive = scene.primitives.get(0); + let primitive; + await pollToPromise(function () { + primitive = scene.primitives.get(0); + return defined(primitive); + }); // wait till the model is loaded before we can check node transformations - return pollToPromise(function () { + await pollToPromise(function () { scene.render(); return primitive.ready; - }).then(function () { - visualizer.update(time); - - const node = primitive.getNode("Root"); - expect(node).toBeDefined(); }); + + visualizer.update(time); + + const node = primitive.getNode("Root"); + expect(node).toBeDefined(); }); - it("removing removes primitives.", function () { + it("removes primitives on Entity removal", async function () { const model = new ModelGraphics(); model.uri = new ConstantProperty(boxUrl); @@ -326,14 +335,19 @@ describe( testObject.model = model; visualizer.update(time); - expect(scene.primitives.length).toEqual(1); + let primitive; + await pollToPromise(function () { + primitive = scene.primitives.get(0); + return defined(primitive); + }); + visualizer.update(time); entityCollection.removeAll(); visualizer.update(time); expect(scene.primitives.length).toEqual(0); }); - it("Visualizer sets id property.", function () { + it("sets id property", async function () { const time = JulianDate.now(); const testObject = entityCollection.getOrCreateEntity("test"); const model = new ModelGraphics(); @@ -345,11 +359,16 @@ describe( model.uri = new ConstantProperty(boxUrl); visualizer.update(time); - const modelPrimitive = scene.primitives.get(0); - expect(modelPrimitive.id).toEqual(testObject); + let primitive; + await pollToPromise(function () { + primitive = scene.primitives.get(0); + return defined(primitive); + }); + + expect(primitive.id).toEqual(testObject); }); - it("Computes bounding sphere.", function () { + it("computes bounding sphere", async function () { const time = JulianDate.now(); const testObject = entityCollection.getOrCreateEntity("test"); const model = new ModelGraphics(); @@ -361,26 +380,30 @@ describe( model.uri = new ConstantProperty(boxUrl); visualizer.update(time); - const modelPrimitive = scene.primitives.get(0); + let primitive; + await pollToPromise(function () { + primitive = scene.primitives.get(0); + return defined(primitive); + }); + const result = new BoundingSphere(); let state = visualizer.getBoundingSphere(testObject, result); expect(state).toBe(BoundingSphereState.PENDING); - return pollToPromise(function () { + await pollToPromise(function () { scene.render(); state = visualizer.getBoundingSphere(testObject, result); return state !== BoundingSphereState.PENDING; - }).then(function () { - expect(state).toBe(BoundingSphereState.DONE); - const expected = BoundingSphere.clone( - modelPrimitive.boundingSphere, - new BoundingSphere() - ); - expect(result).toEqual(expected); }); + expect(state).toBe(BoundingSphereState.DONE); + const expected = BoundingSphere.clone( + primitive.boundingSphere, + new BoundingSphere() + ); + expect(result).toEqual(expected); }); - it("Computes bounding sphere with height reference clamp to ground", function () { + it("computes bounding sphere with height reference clamp to ground", function () { // Setup a position for the model. const position = Cartesian3.fromDegrees(149.515332, -34.984799); const positionCartographic = Cartographic.fromCartesian(position); @@ -455,7 +478,7 @@ describe( }); }); - it("Computes bounding sphere with height reference clamp to ground on terrain provider without availability", function () { + it("computes bounding sphere with height reference clamp to ground on terrain provider without availability", function () { // Setup a position for the model. const longitude = CesiumMath.toRadians(149.515332); const latitude = CesiumMath.toRadians(-34.984799); @@ -507,7 +530,7 @@ describe( }); }); - it("Computes bounding sphere with height reference relative to ground", function () { + it("computes bounding sphere with height reference relative to ground", function () { // Setup a position for the model. const heightOffset = 1000.0; const position = Cartesian3.fromDegrees( @@ -588,7 +611,7 @@ describe( }); }); - it("Computes bounding sphere with height reference relative to ground on terrain provider without availability", function () { + it("computes bounding sphere with height reference relative to ground on terrain provider without availability", function () { // Setup a position for the model. const longitude = CesiumMath.toRadians(149.515332); const latitude = CesiumMath.toRadians(-34.984799); @@ -638,7 +661,7 @@ describe( }); }); - it("Fails bounding sphere for entity without billboard.", function () { + it("fails bounding sphere for entity without ModelGraphics", function () { const testObject = entityCollection.getOrCreateEntity("test"); visualizer.update(JulianDate.now()); const result = new BoundingSphere(); @@ -646,7 +669,7 @@ describe( expect(state).toBe(BoundingSphereState.FAILED); }); - it("Fails bounding sphere when model fails to load.", function () { + it("fails bounding sphere when model fails to load", function () { const time = JulianDate.now(); const testObject = entityCollection.getOrCreateEntity("test"); const model = new ModelGraphics(); @@ -670,7 +693,7 @@ describe( }); }); - it("Fails bounding sphere when sampleTerrainMostDetailed fails.", function () { + it("fails bounding sphere when sampleTerrainMostDetailed fails", function () { // Setup a position for the model. const heightOffset = 1000.0; const position = Cartesian3.fromDegrees( @@ -727,14 +750,14 @@ describe( }); }); - it("Compute bounding sphere throws without entity.", function () { + it("compute bounding sphere throws without entity", function () { const result = new BoundingSphere(); expect(function () { visualizer.getBoundingSphere(undefined, result); }).toThrowDeveloperError(); }); - it("Compute bounding sphere throws without result.", function () { + it("compute bounding sphere throws without result", function () { const testObject = entityCollection.getOrCreateEntity("test"); expect(function () { visualizer.getBoundingSphere(testObject, undefined); diff --git a/packages/engine/Specs/Scene/BufferLoaderSpec.js b/packages/engine/Specs/Scene/BufferLoaderSpec.js index f567b8821ee7..884ab8e001e9 100644 --- a/packages/engine/Specs/Scene/BufferLoaderSpec.js +++ b/packages/engine/Specs/Scene/BufferLoaderSpec.js @@ -1,4 +1,9 @@ -import { BufferLoader, Resource, ResourceCache } from "../../index.js"; +import { + BufferLoader, + Resource, + ResourceCache, + RuntimeError, +} from "../../index.js"; describe("Scene/BufferLoader", function () { const typedArray = new Uint8Array([1, 3, 7, 15, 31, 63, 127, 255]); @@ -27,7 +32,7 @@ describe("Scene/BufferLoader", function () { }).toThrowDeveloperError(); }); - it("rejects promise if buffer cannot be fetched", function () { + it("load throws if buffer cannot be fetched", async function () { const error = new Error("404 Not Found"); spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { return Promise.reject(error); @@ -37,31 +42,23 @@ describe("Scene/BufferLoader", function () { resource: resource, }); - bufferLoader.load(); - - return bufferLoader.promise - .then(function (bufferLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load external buffer: https://example.com/external.bin\n404 Not Found" - ); - }); + await expectAsync(bufferLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load external buffer: https://example.com/external.bin\n404 Not Found" + ); }); - it("loads buffer from typed array", function () { + it("loads buffer from typed array", async function () { const bufferLoader = new BufferLoader({ typedArray: typedArray, }); - bufferLoader.load(); - return bufferLoader.promise.then(function (bufferLoader) { - expect(bufferLoader.typedArray).toBe(typedArray); - }); + await bufferLoader.load(); + + expect(bufferLoader.typedArray).toBe(typedArray); }); - it("loads external buffer", function () { + it("loads external buffer", async function () { const fetchBuffer = spyOn( Resource.prototype, "fetchArrayBuffer" @@ -71,15 +68,13 @@ describe("Scene/BufferLoader", function () { resource: resource, }); - bufferLoader.load(); + await bufferLoader.load(); - return bufferLoader.promise.then(function (bufferLoader) { - expect(fetchBuffer).toHaveBeenCalled(); - expect(bufferLoader.typedArray.buffer).toBe(arrayBuffer); - }); + expect(fetchBuffer).toHaveBeenCalled(); + expect(bufferLoader.typedArray.buffer).toBe(arrayBuffer); }); - it("destroys buffer", function () { + it("destroys buffer", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -90,27 +85,20 @@ describe("Scene/BufferLoader", function () { expect(bufferLoader.typedArray).not.toBeDefined(); - bufferLoader.load(); + await bufferLoader.load(); - return bufferLoader.promise.then(function (bufferLoader) { - expect(bufferLoader.typedArray.buffer).toBe(arrayBuffer); - expect(bufferLoader.isDestroyed()).toBe(false); + expect(bufferLoader.typedArray.buffer).toBe(arrayBuffer); + expect(bufferLoader.isDestroyed()).toBe(false); - bufferLoader.destroy(); - expect(bufferLoader.typedArray).not.toBeDefined(); - expect(bufferLoader.isDestroyed()).toBe(true); - }); + bufferLoader.destroy(); + expect(bufferLoader.typedArray).not.toBeDefined(); + expect(bufferLoader.isDestroyed()).toBe(true); }); - function resolveAfterDestroy(rejectPromise) { - const fetchPromise = new Promise(function (resolve, reject) { - if (rejectPromise) { - reject(new Error()); - } else { - resolve(arrayBuffer); - } - }); - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(fetchPromise); + it("handles asynchronous load after destroy", async function () { + spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( + Promise.resolve(arrayBuffer) + ); const bufferLoader = new BufferLoader({ resource: resource, @@ -121,17 +109,27 @@ describe("Scene/BufferLoader", function () { const loadPromise = bufferLoader.load(); bufferLoader.destroy(); - return loadPromise.finally(function () { - expect(bufferLoader.typedArray).not.toBeDefined(); - expect(bufferLoader.isDestroyed()).toBe(true); + await expectAsync(loadPromise).toBeResolved(); + expect(bufferLoader.typedArray).not.toBeDefined(); + expect(bufferLoader.isDestroyed()).toBe(true); + }); + + it("handles asynchronous error after destroy", async function () { + spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( + Promise.reject(new Error()) + ); + + const bufferLoader = new BufferLoader({ + resource: resource, }); - } - it("handles resolving uri after destroy", function () { - return resolveAfterDestroy(false); - }); + expect(bufferLoader.typedArray).not.toBeDefined(); + + const loadPromise = bufferLoader.load(); + bufferLoader.destroy(); - it("handles rejecting uri after destroy", function () { - return resolveAfterDestroy(true); + await expectAsync(loadPromise).toBeResolved(); + expect(bufferLoader.typedArray).not.toBeDefined(); + expect(bufferLoader.isDestroyed()).toBe(true); }); }); diff --git a/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 12f7b341e9c0..ec5eb154ca05 100644 --- a/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -1210,12 +1210,12 @@ describe( expect(clippingPlanes.isDestroyed()).toBe(true); }); - it("throws a DeveloperError when given a ClippingPlaneCollection attached to a Model", function () { + it("throws a DeveloperError when given a ClippingPlaneCollection attached to a Model", async function () { const clippingPlanes = new ClippingPlaneCollection({ planes: [new ClippingPlane(Cartesian3.UNIT_Z, 10000000.0)], }); const model = scene.primitives.add( - Model.fromGltf({ + await Model.fromGltf({ url: "./Data/Models/glTF-2.0/BoxTextured/glTF/BoxTextured.gltf", }) ); diff --git a/packages/engine/Specs/Scene/GltfBufferViewLoaderSpec.js b/packages/engine/Specs/Scene/GltfBufferViewLoaderSpec.js index 1a5009e12e04..97fb9610212e 100644 --- a/packages/engine/Specs/Scene/GltfBufferViewLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfBufferViewLoaderSpec.js @@ -3,6 +3,7 @@ import { GltfBufferViewLoader, Resource, ResourceCache, + RuntimeError, } from "../../index.js"; describe("Scene/GltfBufferViewLoader", function () { @@ -101,9 +102,6 @@ describe("Scene/GltfBufferViewLoader", function () { const gltfResource = new Resource({ url: gltfUri, }); - const bufferResource = new Resource({ - url: "https://example.com/external.bin", - }); afterEach(function () { ResourceCache.clearForSpecs(); @@ -169,11 +167,10 @@ describe("Scene/GltfBufferViewLoader", function () { }).toThrowDeveloperError(); }); - it("rejects promise if buffer fails to load", function () { - const error = new Error("404 Not Found"); - spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { - return Promise.reject(error); - }); + it("load throws if buffer fails to load", async function () { + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(() => + Promise.reject(new Error("404 Not Found")) + ); const bufferViewLoader = new GltfBufferViewLoader({ resourceCache: ResourceCache, @@ -183,26 +180,21 @@ describe("Scene/GltfBufferViewLoader", function () { baseResource: gltfResource, }); - bufferViewLoader.load(); - - return bufferViewLoader.promise - .then(function (bufferViewLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" - ); - }); + await expectAsync(bufferViewLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" + ); }); - it("loads buffer view for embedded buffer", function () { - ResourceCache.loadEmbeddedBuffer({ + it("loads buffer view for embedded buffer", async function () { + const bufferLoader = ResourceCache.getEmbeddedBufferLoader({ parentResource: gltfResource, bufferId: 0, typedArray: bufferTypedArray, }); + await bufferLoader.load(); + const bufferViewLoader = new GltfBufferViewLoader({ resourceCache: ResourceCache, gltf: gltfEmbedded, @@ -210,14 +202,13 @@ describe("Scene/GltfBufferViewLoader", function () { gltfResource: gltfResource, baseResource: gltfResource, }); - bufferViewLoader.load(); - return bufferViewLoader.promise.then(function (bufferViewLoader) { - expect(bufferViewLoader.typedArray).toEqual(new Uint8Array([7, 15, 31])); - }); + await bufferViewLoader.load(); + + expect(bufferViewLoader.typedArray).toEqual(new Uint8Array([7, 15, 31])); }); - it("loads buffer view for external buffer", function () { + it("loads buffer view for external buffer", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(bufferArrayBuffer) ); @@ -229,14 +220,13 @@ describe("Scene/GltfBufferViewLoader", function () { gltfResource: gltfResource, baseResource: gltfResource, }); - bufferViewLoader.load(); - return bufferViewLoader.promise.then(function (bufferViewLoader) { - expect(bufferViewLoader.typedArray).toEqual(new Uint8Array([7, 15, 31])); - }); + await bufferViewLoader.load(); + + expect(bufferViewLoader.typedArray).toEqual(new Uint8Array([7, 15, 31])); }); - it("destroys buffer view", function () { + it("destroys buffer view", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(bufferArrayBuffer) ); @@ -256,27 +246,27 @@ describe("Scene/GltfBufferViewLoader", function () { expect(bufferViewLoader.typedArray).not.toBeDefined(); - bufferViewLoader.load(); + await bufferViewLoader.load(); - return bufferViewLoader.promise.then(function (bufferViewLoader) { - expect(bufferViewLoader.typedArray).toEqual(new Uint8Array([7, 15, 31])); - expect(bufferViewLoader.isDestroyed()).toBe(false); + expect(bufferViewLoader.typedArray).toEqual(new Uint8Array([7, 15, 31])); + expect(bufferViewLoader.isDestroyed()).toBe(false); - bufferViewLoader.destroy(); + bufferViewLoader.destroy(); - expect(bufferViewLoader.typedArray).not.toBeDefined(); - expect(bufferViewLoader.isDestroyed()).toBe(true); - expect(unloadBuffer).toHaveBeenCalled(); - }); + expect(bufferViewLoader.typedArray).not.toBeDefined(); + expect(bufferViewLoader.isDestroyed()).toBe(true); + expect(unloadBuffer).toHaveBeenCalled(); }); - it("decodes positions with EXT_meshopt_compression", function () { - const bufferLoader = ResourceCache.loadEmbeddedBuffer({ + it("decodes positions with EXT_meshopt_compression", async function () { + const bufferLoader = ResourceCache.getEmbeddedBufferLoader({ parentResource: gltfResource, bufferId: 0, typedArray: meshoptPositionTypedArray, }); + await bufferLoader.load(); + const bufferViewLoader = new GltfBufferViewLoader({ resourceCache: ResourceCache, gltf: meshoptGltfEmbedded, @@ -285,35 +275,18 @@ describe("Scene/GltfBufferViewLoader", function () { baseResource: gltfResource, }); - bufferViewLoader.load(); - return bufferLoader.promise - .then(function () { - bufferViewLoader.process({}); - return bufferViewLoader.promise; - }) - .then(function (bufferViewLoader) { - const decodedPositionBase64 = getBase64FromTypedArray( - bufferViewLoader.typedArray - ); - expect(decodedPositionBase64).toEqual(fallbackPositionBufferBase64); - }); - }); + await bufferViewLoader.load(); - function resolveAfterDestroy(rejectPromise) { - const fetchPromise = new Promise(function (resolve, reject) { - if (rejectPromise) { - reject(new Error()); - } else { - resolve(bufferArrayBuffer); - } - }); - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(fetchPromise); + const decodedPositionBase64 = getBase64FromTypedArray( + bufferViewLoader.typedArray + ); + expect(decodedPositionBase64).toEqual(fallbackPositionBufferBase64); + }); - // Load a copy of the buffer into the cache so that the buffer promise - // resolves even if the buffer view loader is destroyed - const bufferLoaderCopy = ResourceCache.loadExternalBuffer({ - resource: bufferResource, - }); + it("handles asynchronous load after destroy", async function () { + spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( + Promise.resolve(bufferArrayBuffer) + ); const bufferViewLoader = new GltfBufferViewLoader({ resourceCache: ResourceCache, @@ -328,19 +301,31 @@ describe("Scene/GltfBufferViewLoader", function () { const loadPromise = bufferViewLoader.load(); bufferViewLoader.destroy(); - return loadPromise.finally(function () { - expect(bufferViewLoader.typedArray).not.toBeDefined(); - expect(bufferViewLoader.isDestroyed()).toBe(true); + await expectAsync(loadPromise).toBeResolved(); + expect(bufferViewLoader.typedArray).not.toBeDefined(); + expect(bufferViewLoader.isDestroyed()).toBe(true); + }); + + it("handles asynchronous error after destroy", async function () { + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(() => + Promise.reject(new Error()) + ); - ResourceCache.unload(bufferLoaderCopy); + const bufferViewLoader = new GltfBufferViewLoader({ + resourceCache: ResourceCache, + gltf: gltfExternal, + bufferViewId: 0, + gltfResource: gltfResource, + baseResource: gltfResource, }); - } - it("handles resolving buffer after destroy", function () { - return resolveAfterDestroy(false); - }); + expect(bufferViewLoader.typedArray).not.toBeDefined(); - it("handles rejecting buffer after destroy", function () { - return resolveAfterDestroy(true); + const loadPromise = bufferViewLoader.load(); + bufferViewLoader.destroy(); + + await expectAsync(loadPromise).toBeResolved(); + expect(bufferViewLoader.typedArray).not.toBeDefined(); + expect(bufferViewLoader.isDestroyed()).toBe(true); }); }); diff --git a/packages/engine/Specs/Scene/GltfDracoLoaderSpec.js b/packages/engine/Specs/Scene/GltfDracoLoaderSpec.js index 596e6947a169..4d99a8c995f4 100644 --- a/packages/engine/Specs/Scene/GltfDracoLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfDracoLoaderSpec.js @@ -5,11 +5,10 @@ import { GltfDracoLoader, Resource, ResourceCache, - ResourceLoaderState, + RuntimeError, } from "../../index.js"; import createScene from "../../../../Specs/createScene.js"; import loaderProcess from "../../../../Specs/loaderProcess.js"; -import pollToPromise from "../../../../Specs/pollToPromise.js"; import waitForLoaderProcess from "../../../../Specs/waitForLoaderProcess.js"; describe( @@ -200,7 +199,7 @@ describe( }).toThrowDeveloperError(); }); - it("rejects promise if buffer view fails to load", function () { + it("load throws if buffer view fails to load", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { const error = new Error("404 Not Found"); return Promise.reject(error); @@ -214,20 +213,13 @@ describe( baseResource: gltfResource, }); - dracoLoader.load(); - - return dracoLoader.promise - .then(function (dracoLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load Draco\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" - ); - }); + await expectAsync(dracoLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load Draco\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" + ); }); - it("rejects promise if draco decoding fails", function () { + it("process throws if draco decoding fails", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(bufferArrayBuffer) ); @@ -245,20 +237,18 @@ describe( baseResource: gltfResource, }); - dracoLoader.load(); + await dracoLoader.load(); - return waitForLoaderProcess(dracoLoader, scene) - .then(function (dracoLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load Draco\nDraco decode failed" - ); - }); + await expectAsync( + waitForLoaderProcess(dracoLoader, scene) + ).toBeRejectedWithError( + RuntimeError, + "Failed to load Draco\nDraco decode failed" + ); + expect(() => loaderProcess(dracoLoader, scene)).not.toThrowError(); }); - it("loads draco", function () { + it("loads draco", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(bufferArrayBuffer) ); @@ -281,30 +271,19 @@ describe( baseResource: gltfResource, }); - dracoLoader.load(); - - return pollToPromise(function () { - loaderProcess(dracoLoader, scene); - return ( - dracoLoader._state === ResourceLoaderState.READY || - dracoLoader._state === ResourceLoaderState.FAILED - ); - }) - .then(function () { - return dracoLoader.promise; - }) - .then(function (dracoLoader) { - loaderProcess(dracoLoader, scene); // Check that calling process after load doesn't break anything - expect(dracoLoader.decodedData.indices).toBe( - decodeDracoResults.indexArray - ); - expect(dracoLoader.decodedData.vertexAttributes).toBe( - decodeDracoResults.attributeData - ); - }); + await dracoLoader.load(); + await waitForLoaderProcess(dracoLoader, scene); + + expect(() => loaderProcess(dracoLoader, scene)).not.toThrowError(); + expect(dracoLoader.decodedData.indices).toBe( + decodeDracoResults.indexArray + ); + expect(dracoLoader.decodedData.vertexAttributes).toBe( + decodeDracoResults.attributeData + ); }); - it("destroys draco loader", function () { + it("destroys draco loader", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(bufferArrayBuffer) ); @@ -326,23 +305,20 @@ describe( baseResource: gltfResource, }); - dracoLoader.load(); + await dracoLoader.load(); + await waitForLoaderProcess(dracoLoader, scene); - return waitForLoaderProcess(dracoLoader, scene).then(function ( - dracoLoader - ) { - expect(dracoLoader.decodedData).toBeDefined(); - expect(dracoLoader.isDestroyed()).toBe(false); + expect(dracoLoader.decodedData).toBeDefined(); + expect(dracoLoader.isDestroyed()).toBe(false); - dracoLoader.destroy(); + dracoLoader.destroy(); - expect(dracoLoader.decodedData).not.toBeDefined(); - expect(dracoLoader.isDestroyed()).toBe(true); - expect(unloadBufferView).toHaveBeenCalled(); - }); + expect(dracoLoader.decodedData).not.toBeDefined(); + expect(dracoLoader.isDestroyed()).toBe(true); + expect(unloadBufferView).toHaveBeenCalled(); }); - function resolveBufferViewAfterDestroy(reject) { + async function resolveBufferViewAfterDestroy(reject) { spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { if (reject) { return Promise.reject(new Error()); @@ -355,15 +331,6 @@ describe( Promise.resolve(decodeDracoResults) ); - // Load a copy of the buffer view into the cache so that the buffer view - // promise resolves even if the draco loader is destroyed - const bufferViewLoaderCopy = ResourceCache.loadBufferView({ - gltf: gltfDraco, - bufferViewId: 0, - gltfResource: gltfResource, - baseResource: gltfResource, - }); - const dracoLoader = new GltfDracoLoader({ resourceCache: ResourceCache, gltf: gltfDraco, @@ -376,12 +343,11 @@ describe( const promise = dracoLoader.load(); dracoLoader.destroy(); - return promise.finally(function () { - expect(dracoLoader.decodedData).not.toBeDefined(); - expect(dracoLoader.isDestroyed()).toBe(true); - ResourceCache.unload(bufferViewLoaderCopy); - }); + await expectAsync(promise).toBeResolved(); + + expect(dracoLoader.decodedData).not.toBeDefined(); + expect(dracoLoader.isDestroyed()).toBe(true); } it("handles resolving buffer view after destroy", function () { @@ -392,7 +358,7 @@ describe( return resolveBufferViewAfterDestroy(true); }); - function resolveDracoAfterDestroy(rejectPromise) { + async function resolveDracoAfterDestroy(rejectPromise) { const dracoLoader = new GltfDracoLoader({ resourceCache: ResourceCache, gltf: gltfDraco, @@ -404,7 +370,6 @@ describe( spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { // After we resolve, process again, then destroy setTimeout(function () { - loaderProcess(dracoLoader, scene); dracoLoader.destroy(); }, 1); return Promise.resolve(bufferArrayBuffer); @@ -425,14 +390,12 @@ describe( expect(dracoLoader.decodedData).not.toBeDefined(); - dracoLoader.load(); - loaderProcess(dracoLoader, scene); - return dracoLoader.promise.finally(function () { - expect(decodeBufferView).toHaveBeenCalled(); // Make sure the decode actually starts + await dracoLoader.load(); + await waitForLoaderProcess(dracoLoader, scene); // Destroy happens in mock above + expect(decodeBufferView).toHaveBeenCalled(); // Make sure the decode actually starts - expect(dracoLoader.decodedData).not.toBeDefined(); - expect(dracoLoader.isDestroyed()).toBe(true); - }); + expect(dracoLoader.decodedData).not.toBeDefined(); + expect(dracoLoader.isDestroyed()).toBe(true); } it("handles resolving draco after destroy", function () { diff --git a/packages/engine/Specs/Scene/GltfImageLoaderSpec.js b/packages/engine/Specs/Scene/GltfImageLoaderSpec.js index d36ac465cb4e..ef1310a09c14 100644 --- a/packages/engine/Specs/Scene/GltfImageLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfImageLoaderSpec.js @@ -7,10 +7,10 @@ import { FeatureDetection, Resource, ResourceCache, + RuntimeError, } from "../../index.js"; import createContext from "../../../../Specs/createContext.js"; import dataUriToBuffer from "../../../../Specs/dataUriToBuffer.js"; -import pollToPromise from "../../../../Specs/pollToPromise.js"; describe( "Scene/GltfImageLoader", @@ -170,7 +170,7 @@ describe( }).toThrowDeveloperError(); }); - it("rejects promise if buffer view fails to load", function () { + it("load throws if buffer view fails to load", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { const error = new Error("404 Not Found"); return Promise.reject(error); @@ -184,20 +184,13 @@ describe( baseResource: gltfResource, }); - imageLoader.load(); - - return imageLoader.promise - .then(function (imageLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load embedded image\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" - ); - }); + await expectAsync(imageLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load embedded image\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" + ); }); - it("rejects promise if image format is not recognized", function () { + it("load throws if image format is not recognized", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(gifBuffer) ); @@ -210,20 +203,13 @@ describe( baseResource: gltfResource, }); - imageLoader.load(); - - return imageLoader.promise - .then(function (imageLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load embedded image\nImage format is not recognized" - ); - }); + await expectAsync(imageLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load embedded image\nImage format is not recognized" + ); }); - it("rejects promise if uri fails to load", function () { + it("load throws if uri fails to load", async function () { spyOn(Resource.prototype, "fetchImage").and.callFake(function () { const error = new Error("404 Not Found"); return Promise.reject(error); @@ -237,20 +223,13 @@ describe( baseResource: gltfResource, }); - imageLoader.load(); - - return imageLoader.promise - .then(function (imageLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load image: image.png\n404 Not Found" - ); - }); + await expectAsync(imageLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load image: image.png\n404 Not Found" + ); }); - function loadsFromBufferView(imageBuffer) { + async function loadsFromBufferView(imageBuffer) { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(imageBuffer) ); @@ -263,12 +242,10 @@ describe( baseResource: gltfResource, }); - imageLoader.load(); + await imageLoader.load(); - return imageLoader.promise.then(function (imageLoader) { - expect(imageLoader.image.width).toBe(1); - expect(imageLoader.image.height).toBe(1); - }); + expect(imageLoader.image.width).toBe(1); + expect(imageLoader.image.height).toBe(1); } it("loads PNG from buffer view", function () { @@ -279,19 +256,17 @@ describe( return loadsFromBufferView(jpgBuffer); }); - it("loads WebP from buffer view", function () { - return pollToPromise(function () { - FeatureDetection.supportsWebP.initialize(); - return FeatureDetection.supportsWebP.initialized; - }).then(function () { - if (!FeatureDetection.supportsWebP()) { - return; - } - return loadsFromBufferView(webpBuffer); - }); + it("loads WebP from buffer view", async function () { + await FeatureDetection.supportsWebP.initialize(); + + if (!FeatureDetection.supportsWebP()) { + return; + } + + return loadsFromBufferView(webpBuffer); }); - it("loads KTX2/Basis from buffer view", function () { + it("loads KTX2/Basis from buffer view", async function () { if (!context.supportsBasis) { return; } @@ -308,17 +283,15 @@ describe( baseResource: gltfResource, }); - imageLoader.load(); + await imageLoader.load(); - return imageLoader.promise.then(function (imageLoader) { - expect(imageLoader.image instanceof CompressedTextureBuffer).toBe(true); - expect(imageLoader.image.width).toBe(4); - expect(imageLoader.image.height).toBe(4); - expect(imageLoader.mipLevels).toBeUndefined(); - }); + expect(imageLoader.image instanceof CompressedTextureBuffer).toBe(true); + expect(imageLoader.image.width).toBe(4); + expect(imageLoader.image.height).toBe(4); + expect(imageLoader.mipLevels).toBeUndefined(); }); - it("loads KTX2/Basis with mipmap from buffer view", function () { + it("loads KTX2/Basis with mipmap from buffer view", async function () { if (!context.supportsBasis) { return; } @@ -335,17 +308,15 @@ describe( baseResource: gltfResource, }); - imageLoader.load(); + await imageLoader.load(); - return imageLoader.promise.then(function (imageLoader) { - expect(imageLoader.image instanceof CompressedTextureBuffer).toBe(true); - expect(imageLoader.image.width).toBe(4); - expect(imageLoader.image.height).toBe(4); - expect(imageLoader.mipLevels.length).toBe(2); - }); + expect(imageLoader.image).toBeInstanceOf(CompressedTextureBuffer); + expect(imageLoader.image.width).toBe(4); + expect(imageLoader.image.height).toBe(4); + expect(imageLoader.mipLevels.length).toBe(2); }); - it("loads from uri", function () { + it("loads from uri", async function () { spyOn(Resource.prototype, "fetchImage").and.returnValue( Promise.resolve(image) ); @@ -358,15 +329,13 @@ describe( baseResource: gltfResource, }); - imageLoader.load(); + await imageLoader.load(); - return imageLoader.promise.then(function (imageLoader) { - expect(imageLoader.image.width).toBe(1); - expect(imageLoader.image.height).toBe(1); - }); + expect(imageLoader.image.width).toBe(1); + expect(imageLoader.image.height).toBe(1); }); - it("loads KTX2/Basis from uri ", function () { + it("loads KTX2/Basis from uri ", async function () { if (!context.supportsBasis) { return; } @@ -386,14 +355,12 @@ describe( baseResource: baseResource, }); - imageLoader.load(); + await imageLoader.load(); - return imageLoader.promise.then(function (imageLoader) { - expect(imageLoader.image instanceof CompressedTextureBuffer).toBe(true); - expect(imageLoader.image.width).toBe(4); - expect(imageLoader.image.height).toBe(4); - expect(imageLoader.mipLevels).toBeUndefined(); - }); + expect(imageLoader.image).toBeInstanceOf(CompressedTextureBuffer); + expect(imageLoader.image.width).toBe(4); + expect(imageLoader.image.height).toBe(4); + expect(imageLoader.mipLevels).toBeUndefined(); }); it("match the ktx2 url with the ktx2Regex", function () { @@ -408,7 +375,7 @@ describe( expect(ktx2Regex.test(uri)).toBe(true); }); - it("destroys image loader", function () { + it("destroys image loader", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(pngBuffer) ); @@ -427,38 +394,29 @@ describe( }); expect(imageLoader.image).not.toBeDefined(); - imageLoader.load(); + await imageLoader.load(); - return imageLoader.promise.then(function (imageLoader) { - expect(imageLoader.image).toBeDefined(); - expect(imageLoader.isDestroyed()).toBe(false); + expect(imageLoader.image).toBeDefined(); + expect(imageLoader.isDestroyed()).toBe(false); - imageLoader.destroy(); + imageLoader.destroy(); - expect(imageLoader.image).not.toBeDefined(); - expect(imageLoader.isDestroyed()).toBe(true); - expect(unloadBufferView).toHaveBeenCalled(); - }); + expect(imageLoader.image).not.toBeDefined(); + expect(imageLoader.isDestroyed()).toBe(true); + expect(unloadBufferView).toHaveBeenCalled(); }); - function resolveBufferViewAfterDestroy(rejectPromise) { - const promise = new Promise(function (resolve, reject) { - if (rejectPromise) { - reject(new Error()); - } else { - resolve(pngBuffer); - } - }); - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(promise); - - // Load a copy of the buffer view into the cache so that the buffer view - // promise resolves even if the image loader is destroyed - const bufferViewLoaderCopy = ResourceCache.loadBufferView({ - gltf: getGltf(pngBuffer), - bufferViewId: 0, - gltfResource: gltfResource, - baseResource: gltfResource, - }); + async function resolveBufferViewAfterDestroy(rejectPromise) { + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake( + () => + new Promise(function (resolve, reject) { + if (rejectPromise) { + reject(new Error()); + } else { + resolve(pngBuffer); + } + }) + ); const imageLoader = new GltfImageLoader({ resourceCache: ResourceCache, @@ -470,14 +428,13 @@ describe( expect(imageLoader.image).not.toBeDefined(); - imageLoader.load(); + const loadPromise = imageLoader.load(); imageLoader.destroy(); - return imageLoader.promise.finally(function () { - expect(imageLoader.image).not.toBeDefined(); - expect(imageLoader.isDestroyed()).toBe(true); - ResourceCache.unload(bufferViewLoaderCopy); - }); + await expectAsync(loadPromise).toBeResolved(); + + expect(imageLoader.image).not.toBeDefined(); + expect(imageLoader.isDestroyed()).toBe(true); } it("handles resolving buffer view after destroy", function () { @@ -488,36 +445,22 @@ describe( return resolveBufferViewAfterDestroy(true); }); - function resolveImageFromTypedArrayAfterDestroy(rejectPromise) { + async function resolveImageFromTypedArrayAfterDestroy(rejectPromise) { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(pngBuffer) ); - let promise = new Promise(function (resolve, reject) { - if (rejectPromise) { - reject(new Error()); - } else { - resolve(image); - } - }); - if (rejectPromise) { - promise = promise.catch(function (e) { - // swallow that error we just threw - }); - } - spyOn(GltfImageLoader, "_loadImageFromTypedArray").and.returnValue( - promise + spyOn(GltfImageLoader, "_loadImageFromTypedArray").and.callFake( + () => + new Promise(function (resolve, reject) { + if (rejectPromise) { + reject(new Error()); + } else { + resolve(image); + } + }) ); - // Load a copy of the buffer view into the cache so that the buffer view - // promise resolves even if the image loader is destroyed - const bufferViewLoaderCopy = ResourceCache.loadBufferView({ - gltf: getGltf(pngBuffer), - bufferViewId: 0, - gltfResource: gltfResource, - baseResource: gltfResource, - }); - const imageLoader = new GltfImageLoader({ resourceCache: ResourceCache, gltf: getGltf(pngBuffer), @@ -528,14 +471,12 @@ describe( expect(imageLoader.image).not.toBeDefined(); - imageLoader.load(); + const loadPromise = imageLoader.load(); imageLoader.destroy(); - return promise.then(function () { - expect(imageLoader.image).not.toBeDefined(); - expect(imageLoader.isDestroyed()).toBe(true); - ResourceCache.unload(bufferViewLoaderCopy); - }); + await expectAsync(loadPromise).toBeResolved(); + expect(imageLoader.image).not.toBeDefined(); + expect(imageLoader.isDestroyed()).toBe(true); } it("handles resolving image from typed array after destroy", function () { @@ -546,20 +487,17 @@ describe( return resolveImageFromTypedArrayAfterDestroy(true); }); - function resolveUriAfterDestroy(rejectPromise) { - let promise = new Promise(function (resolve, reject) { - if (rejectPromise) { - reject(new Error()); - } else { - resolve(image); - } - }); - if (rejectPromise) { - promise = promise.catch(function (e) { - // swallow that error we just threw - }); - } - spyOn(Resource.prototype, "fetchImage").and.returnValue(promise); + async function resolveUriAfterDestroy(rejectPromise) { + spyOn(Resource.prototype, "fetchImage").and.callFake( + () => + new Promise(function (resolve, reject) { + if (rejectPromise) { + reject(new Error()); + } else { + resolve(image); + } + }) + ); const imageLoader = new GltfImageLoader({ resourceCache: ResourceCache, @@ -571,12 +509,13 @@ describe( expect(imageLoader.image).not.toBeDefined(); - imageLoader.load(); + const loadPromise = imageLoader.load(); imageLoader.destroy(); - return promise.then(function () { - expect(imageLoader.image).not.toBeDefined(); - expect(imageLoader.isDestroyed()).toBe(true); - }); + + await expectAsync(loadPromise).toBeResolved(); + + expect(imageLoader.image).not.toBeDefined(); + expect(imageLoader.isDestroyed()).toBe(true); } it("handles resolving uri after destroy", function () { diff --git a/packages/engine/Specs/Scene/GltfIndexBufferLoaderSpec.js b/packages/engine/Specs/Scene/GltfIndexBufferLoaderSpec.js index ea8e5fc5f2f6..ecd36f1209d8 100644 --- a/packages/engine/Specs/Scene/GltfIndexBufferLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfIndexBufferLoaderSpec.js @@ -9,6 +9,7 @@ import { JobScheduler, Resource, ResourceCache, + RuntimeError, } from "../../index.js"; import concatTypedArrays from "../../../../Specs/concatTypedArrays.js"; import createScene from "../../../../Specs/createScene.js"; @@ -348,11 +349,10 @@ describe( }).toThrowDeveloperError(); }); - it("rejects promise if buffer view fails to load", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { - const error = new Error("404 Not Found"); - return Promise.reject(error); - }); + it("load throws if buffer view fails to load", async function () { + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(() => + Promise.reject(new Error("404 Not Found")) + ); const indexBufferLoader = new GltfIndexBufferLoader({ resourceCache: ResourceCache, @@ -363,20 +363,13 @@ describe( loadBuffer: true, }); - indexBufferLoader.load(); - - return indexBufferLoader.promise - .then(function (indexBufferLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load index buffer\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" - ); - }); + await expectAsync(indexBufferLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load index buffer\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" + ); }); - it("rejects promise if draco fails to load", function () { + it("process throws if draco fails to load", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(dracoArrayBuffer) ); @@ -396,20 +389,16 @@ describe( loadBuffer: true, }); - indexBufferLoader.load(); - - return waitForLoaderProcess(indexBufferLoader, scene) - .then(function (indexBufferLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load index buffer\nFailed to load Draco\nDraco decode failed" - ); - }); + await indexBufferLoader.load(); + await expectAsync( + waitForLoaderProcess(indexBufferLoader, scene) + ).toBeRejectedWithError( + RuntimeError, + "Failed to load index buffer\nFailed to load Draco\nDraco decode failed" + ); }); - it("loads from accessor into buffer", function () { + it("loads from accessor into buffer", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -438,20 +427,20 @@ describe( loadBuffer: true, }); - indexBufferLoader.load(); + await indexBufferLoader.load(); + await waitForLoaderProcess(indexBufferLoader, scene); - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - loaderProcess(indexBufferLoader, scene); // Check that calling process after load doesn't break anything - expect(indexBufferLoader.buffer.sizeInBytes).toBe( - indicesUint16.byteLength - ); - expect(indexBufferLoader.typedArray).toBeUndefined(); - }); + expect(() => loaderProcess(indexBufferLoader, scene)).not.toThrow(); + expect(indexBufferLoader.buffer.sizeInBytes).toBe( + indicesUint16.byteLength + ); + expect(indexBufferLoader.typedArray).toBeUndefined(); + expect(ResourceCache.statistics.geometryByteLength).toBe( + indexBufferLoader.buffer.sizeInBytes + ); }); - it("loads from accessor as typed array", function () { + it("loads from accessor as typed array", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -467,20 +456,20 @@ describe( loadTypedArray: true, }); - indexBufferLoader.load(); + await indexBufferLoader.load(); - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.typedArray.byteLength).toBe( - indicesUint16.byteLength - ); - expect(indexBufferLoader.buffer).toBeUndefined(); - expect(Buffer.createIndexBuffer.calls.count()).toBe(0); - }); + await waitForLoaderProcess(indexBufferLoader, scene); + expect(indexBufferLoader.typedArray.byteLength).toBe( + indicesUint16.byteLength + ); + expect(indexBufferLoader.buffer).toBeUndefined(); + expect(Buffer.createIndexBuffer.calls.count()).toBe(0); + expect(ResourceCache.statistics.geometryByteLength).toBe( + indexBufferLoader.typedArray.byteLength + ); }); - it("loads from accessor as buffer and typed array", function () { + it("loads from accessor as buffer and typed array", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -495,21 +484,21 @@ describe( loadTypedArray: true, }); - indexBufferLoader.load(); + await indexBufferLoader.load(); + await waitForLoaderProcess(indexBufferLoader, scene); - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.buffer.sizeInBytes).toBe( - indicesUint16.byteLength - ); - expect(indexBufferLoader.typedArray.byteLength).toBe( - indicesUint16.byteLength - ); - }); + expect(indexBufferLoader.buffer.sizeInBytes).toBe( + indicesUint16.byteLength + ); + expect(indexBufferLoader.typedArray.byteLength).toBe( + indicesUint16.byteLength + ); + expect(ResourceCache.statistics.geometryByteLength).toBe( + 2 * indexBufferLoader.typedArray.byteLength + ); }); - it("creates index buffer synchronously", function () { + it("creates index buffer synchronously", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -524,18 +513,15 @@ describe( loadBuffer: true, }); - indexBufferLoader.load(); + await indexBufferLoader.load(); + await waitForLoaderProcess(indexBufferLoader, scene); - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.buffer.sizeInBytes).toBe( - indicesUint16.byteLength - ); - }); + expect(indexBufferLoader.buffer.sizeInBytes).toBe( + indicesUint16.byteLength + ); }); - function loadIndices(accessorId, expectedByteLength) { + async function loadIndices(accessorId, expectedByteLength) { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -549,13 +535,10 @@ describe( loadBuffer: true, }); - indexBufferLoader.load(); + await indexBufferLoader.load(); + await waitForLoaderProcess(indexBufferLoader, scene); - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.buffer.sizeInBytes).toBe(expectedByteLength); - }); + expect(indexBufferLoader.buffer.sizeInBytes).toBe(expectedByteLength); } it("loads uint32 indices", function () { @@ -574,7 +557,7 @@ describe( return loadIndices(4, indicesUint8.byteLength); }); - it("loads from draco", function () { + it("loads from draco", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -599,19 +582,19 @@ describe( loadBuffer: true, }); - indexBufferLoader.load(); + await indexBufferLoader.load(); + await waitForLoaderProcess(indexBufferLoader, scene); - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - loaderProcess(indexBufferLoader, scene); // Check that calling process after load doesn't break anything - expect(indexBufferLoader.buffer.sizeInBytes).toBe( - decodedIndices.byteLength - ); - }); + expect(() => loaderProcess(indexBufferLoader, scene)).not.toThrow(); + expect(indexBufferLoader.buffer.sizeInBytes).toBe( + decodedIndices.byteLength + ); + expect(ResourceCache.statistics.geometryByteLength).toBe( + indexBufferLoader.buffer.sizeInBytes + ); }); - it("uses the decoded data's type instead of the accessor component type", function () { + it("uses the decoded data's type instead of the accessor component type", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -633,17 +616,14 @@ describe( loadBuffer: true, }); - indexBufferLoader.load(); + await indexBufferLoader.load(); + await waitForLoaderProcess(indexBufferLoader, scene); - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.indexDatatype).toBe(5123); - expect(indexBufferLoader.buffer.indexDatatype).toBe(5123); - }); + expect(indexBufferLoader.indexDatatype).toBe(5123); + expect(indexBufferLoader.buffer.indexDatatype).toBe(5123); }); - it("destroys index buffer loaded from buffer view", function () { + it("destroys index buffer loaded from buffer view", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -667,24 +647,21 @@ describe( loadBuffer: true, }); - indexBufferLoader.load(); + await indexBufferLoader.load(); + await waitForLoaderProcess(indexBufferLoader, scene); - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.buffer).toBeDefined(); - expect(indexBufferLoader.isDestroyed()).toBe(false); + expect(indexBufferLoader.buffer).toBeDefined(); + expect(indexBufferLoader.isDestroyed()).toBe(false); - indexBufferLoader.destroy(); + indexBufferLoader.destroy(); - expect(indexBufferLoader.buffer).not.toBeDefined(); - expect(indexBufferLoader.isDestroyed()).toBe(true); - expect(unloadBufferView).toHaveBeenCalled(); - expect(destroyIndexBuffer).toHaveBeenCalled(); - }); + expect(indexBufferLoader.buffer).not.toBeDefined(); + expect(indexBufferLoader.isDestroyed()).toBe(true); + expect(unloadBufferView).toHaveBeenCalled(); + expect(destroyIndexBuffer).toHaveBeenCalled(); }); - it("destroys index buffer loaded from draco", function () { + it("destroys index buffer loaded from draco", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -713,24 +690,21 @@ describe( loadBuffer: true, }); - indexBufferLoader.load(); + await indexBufferLoader.load(); + await waitForLoaderProcess(indexBufferLoader, scene); - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.buffer).toBeDefined(); - expect(indexBufferLoader.isDestroyed()).toBe(false); + expect(indexBufferLoader.buffer).toBeDefined(); + expect(indexBufferLoader.isDestroyed()).toBe(false); - indexBufferLoader.destroy(); + indexBufferLoader.destroy(); - expect(indexBufferLoader.buffer).not.toBeDefined(); - expect(indexBufferLoader.isDestroyed()).toBe(true); - expect(unloadDraco).toHaveBeenCalled(); - expect(destroyIndexBuffer).toHaveBeenCalled(); - }); + expect(indexBufferLoader.buffer).not.toBeDefined(); + expect(indexBufferLoader.isDestroyed()).toBe(true); + expect(unloadDraco).toHaveBeenCalled(); + expect(destroyIndexBuffer).toHaveBeenCalled(); }); - function resolveBufferViewAfterDestroy(rejectPromise) { + async function resolveBufferViewAfterDestroy(rejectPromise) { const indexBufferLoader = new GltfIndexBufferLoader({ resourceCache: ResourceCache, gltf: gltfUncompressed, @@ -740,24 +714,21 @@ describe( loadBuffer: true, }); - const promise = new Promise(function (resolve, reject) { - if (rejectPromise) { - reject(new Error()); - } else { - resolve(arrayBuffer); - } - }); - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(promise); + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(() => + rejectPromise + ? Promise.reject(new Error()) + : Promise.resolve(arrayBuffer) + ); expect(indexBufferLoader.buffer).not.toBeDefined(); - indexBufferLoader.load(); + const promise = indexBufferLoader.load(); indexBufferLoader.destroy(); - return indexBufferLoader.promise.then(function () { - expect(indexBufferLoader.buffer).not.toBeDefined(); - expect(indexBufferLoader.isDestroyed()).toBe(true); - }); + await expectAsync(promise).toBeResolved(); + + expect(indexBufferLoader.buffer).not.toBeDefined(); + expect(indexBufferLoader.isDestroyed()).toBe(true); } it("handles resolving buffer view after destroy", function () { @@ -768,7 +739,7 @@ describe( return resolveBufferViewAfterDestroy(true); }); - function resolveDracoAfterDestroy(rejectPromise) { + async function resolveDracoAfterDestroy(rejectPromise) { const indexBufferLoader = new GltfIndexBufferLoader({ resourceCache: ResourceCache, gltf: gltfDraco, @@ -782,7 +753,6 @@ describe( spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { // After we resolve, process again, then destroy setTimeout(function () { - loaderProcess(indexBufferLoader, scene); indexBufferLoader.destroy(); }, 1); return Promise.resolve(arrayBuffer); @@ -803,14 +773,14 @@ describe( expect(indexBufferLoader.buffer).not.toBeDefined(); - indexBufferLoader.load(); - loaderProcess(indexBufferLoader, scene); - return indexBufferLoader.promise.then(function () { - expect(decodeBufferView).toHaveBeenCalled(); // Make sure the decode actually starts + await indexBufferLoader.load(); // Destroy is called in mock function above + await expectAsync( + waitForLoaderProcess(indexBufferLoader, scene) + ).toBeResolved(); + expect(decodeBufferView).toHaveBeenCalled(); // Make sure the decode actually starts - expect(indexBufferLoader.buffer).not.toBeDefined(); - expect(indexBufferLoader.isDestroyed()).toBe(true); - }); + expect(indexBufferLoader.buffer).not.toBeDefined(); + expect(indexBufferLoader.isDestroyed()).toBe(true); } it("handles resolving draco after destroy", function () { diff --git a/packages/engine/Specs/Scene/GltfJsonLoaderSpec.js b/packages/engine/Specs/Scene/GltfJsonLoaderSpec.js index c069814b0cd5..96561ba3c9a5 100644 --- a/packages/engine/Specs/Scene/GltfJsonLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfJsonLoaderSpec.js @@ -4,6 +4,7 @@ import { GltfJsonLoader, Resource, ResourceCache, + RuntimeError, } from "../../index.js"; import generateJsonBuffer from "../../../../Specs/generateJsonBuffer.js"; @@ -579,7 +580,7 @@ describe("Scene/GltfJsonLoader", function () { }).toThrowDeveloperError(); }); - it("rejects promise if resource fails to load", function () { + it("load throws if resource fails to load", async function () { spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.callFake(function () { const error = new Error("404 Not Found"); return Promise.reject(error); @@ -591,20 +592,13 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); - - return gltfJsonLoader.promise - .then(function (gltfJsonLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load glTF: https://example.com/model.glb\n404 Not Found" - ); - }); + await expectAsync(gltfJsonLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load glTF: https://example.com/model.glb\n404 Not Found" + ); }); - it("rejects promise if glTF fails to process", function () { + it("load throws if glTF fails to process", async function () { const arrayBuffer = generateJsonBuffer(gltf1).buffer; spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( @@ -622,20 +616,13 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); - - return gltfJsonLoader.promise - .then(function (gltfJsonLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load glTF: https://example.com/model.glb\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" - ); - }); + await expectAsync(gltfJsonLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load glTF: https://example.com/model.glb\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" + ); }); - it("rejects promise if glTF fails to process from typed array", function () { + it("load throws if glTF fails to process from typed array", async function () { const typedArray = createGlb1(gltf1); spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { @@ -650,20 +637,13 @@ describe("Scene/GltfJsonLoader", function () { typedArray: typedArray, }); - gltfJsonLoader.load(); - - return gltfJsonLoader.promise - .then(function (gltfJsonLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load glTF: https://example.com/model.glb\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" - ); - }); + await expectAsync(gltfJsonLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load glTF: https://example.com/model.glb\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" + ); }); - it("loads glTF 1.0", function () { + it("loads glTF 1.0", async function () { const arrayBuffer = generateJsonBuffer(gltf1).buffer; spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( @@ -680,15 +660,13 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); + await gltfJsonLoader.load(); - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - const gltf = gltfJsonLoader.gltf; - expect(gltf).toEqual(gltf2Updated); - }); + const gltf = gltfJsonLoader.gltf; + expect(gltf).toEqual(gltf2Updated); }); - it("loads glTF 1.0 with KHR_materials_common", function () { + it("loads glTF 1.0 with KHR_materials_common", async function () { const arrayBuffer = generateJsonBuffer(gltf1MaterialsCommon).buffer; spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( @@ -705,15 +683,13 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); + await gltfJsonLoader.load(); - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - const gltf = gltfJsonLoader.gltf; - expect(gltf).toEqual(gltf2Updated); - }); + const gltf = gltfJsonLoader.gltf; + expect(gltf).toEqual(gltf2Updated); }); - it("loads glTF 1.0 binary", function () { + it("loads glTF 1.0 binary", async function () { const gltf1Binary = clone(gltf1, true); gltf1Binary.buffers = { binary_glTF: { @@ -741,15 +717,13 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); + await gltfJsonLoader.load(); - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - const gltf = gltfJsonLoader.gltf; - expect(gltf).toEqual(gltf1BinaryUpdated); - }); + const gltf = gltfJsonLoader.gltf; + expect(gltf).toEqual(gltf1BinaryUpdated); }); - it("loads glTF 1.0 with data uri", function () { + it("loads glTF 1.0 with data uri", async function () { const gltf1DataUri = clone(gltf1, true); gltf1DataUri.buffers.buffer = { uri: "data:application/octet-stream;base64,AAAAAAAAAAAAAAAA", @@ -770,15 +744,12 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); - - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - const gltf = gltfJsonLoader.gltf; - expect(gltf).toEqual(gltf1DataUriUpdated); - }); + await gltfJsonLoader.load(); + const gltf = gltfJsonLoader.gltf; + expect(gltf).toEqual(gltf1DataUriUpdated); }); - it("loads glTF 2.0", function () { + it("loads glTF 2.0", async function () { const arrayBuffer = generateJsonBuffer(gltf2).buffer; spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( @@ -795,15 +766,13 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); + await gltfJsonLoader.load(); - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - const gltf = gltfJsonLoader.gltf; - expect(gltf).toEqual(gltf2Updated); - }); + const gltf = gltfJsonLoader.gltf; + expect(gltf).toEqual(gltf2Updated); }); - it("loads glTF 2.0 with KHR_techniques_webgl", function () { + it("loads glTF 2.0 with KHR_techniques_webgl", async function () { const arrayBuffer = generateJsonBuffer(gltf2TechniquesWebgl).buffer; spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( @@ -820,15 +789,12 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); - - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - const gltf = gltfJsonLoader.gltf; - expect(gltf).toEqual(gltf2Updated); - }); + await gltfJsonLoader.load(); + const gltf = gltfJsonLoader.gltf; + expect(gltf).toEqual(gltf2Updated); }); - it("loads glTF 2.0 binary", function () { + it("loads glTF 2.0 binary", async function () { const gltf2Binary = clone(gltf2, true); delete gltf2Binary.buffers[0].uri; @@ -847,15 +813,13 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); + await gltfJsonLoader.load(); - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - const gltf = gltfJsonLoader.gltf; - expect(gltf).toEqual(gltf2BinaryUpdated); - }); + const gltf = gltfJsonLoader.gltf; + expect(gltf).toEqual(gltf2BinaryUpdated); }); - it("loads glTF 2.0 with data uri", function () { + it("loads glTF 2.0 with data uri", async function () { const gltf2DataUri = clone(gltf2, true); gltf2DataUri.buffers[0].uri = "data:application/octet-stream;base64,AAAAAAAAAAAAAAAA"; @@ -875,15 +839,13 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); + await gltfJsonLoader.load(); - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - const gltf = gltfJsonLoader.gltf; - expect(gltf).toEqual(gltf2DataUriUpdated); - }); + const gltf = gltfJsonLoader.gltf; + expect(gltf).toEqual(gltf2DataUriUpdated); }); - it("loads typed array", function () { + it("loads typed array", async function () { const gltf2Binary = clone(gltf2, true); delete gltf2Binary.buffers[0].uri; @@ -899,15 +861,13 @@ describe("Scene/GltfJsonLoader", function () { typedArray: typedArray, }); - gltfJsonLoader.load(); + await gltfJsonLoader.load(); - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - const gltf = gltfJsonLoader.gltf; - expect(gltf).toEqual(gltf2BinaryUpdated); - }); + const gltf = gltfJsonLoader.gltf; + expect(gltf).toEqual(gltf2BinaryUpdated); }); - it("loads JSON directly", function () { + it("loads JSON directly", async function () { const gltf = clone(gltf2, true); spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( @@ -921,15 +881,13 @@ describe("Scene/GltfJsonLoader", function () { gltfJson: gltf, }); - gltfJsonLoader.load(); + await gltfJsonLoader.load(); - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - const gltf = gltfJsonLoader.gltf; - expect(gltf).toEqual(gltf2Updated); - }); + const loadedGltf = gltfJsonLoader.gltf; + expect(loadedGltf).toEqual(gltf2Updated); }); - it("destroys", function () { + it("destroys", async function () { const gltf2Binary = clone(gltf2, true); delete gltf2Binary.buffers[0].uri; @@ -950,32 +908,23 @@ describe("Scene/GltfJsonLoader", function () { baseResource: gltfResource, }); - gltfJsonLoader.load(); + await gltfJsonLoader.load(); - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - expect(gltfJsonLoader.gltf).toBeDefined(); - expect(gltfJsonLoader.isDestroyed()).toBe(false); + expect(gltfJsonLoader.gltf).toBeDefined(); + expect(gltfJsonLoader.isDestroyed()).toBe(false); - gltfJsonLoader.destroy(); + gltfJsonLoader.destroy(); - expect(gltfJsonLoader.gltf).not.toBeDefined(); - expect(gltfJsonLoader.isDestroyed()).toBe(true); - expect(unloadBuffer).toHaveBeenCalled(); - }); + expect(gltfJsonLoader.gltf).not.toBeDefined(); + expect(gltfJsonLoader.isDestroyed()).toBe(true); + expect(unloadBuffer).toHaveBeenCalled(); }); - function resolvesGltfAfterDestroy(rejectPromise) { + async function resolvesGltfAfterDestroy(rejectPromise) { const arrayBuffer = generateJsonBuffer(gltf2).buffer; - const promise = new Promise(function (resolve, reject) { - if (rejectPromise) { - reject(new Error()); - return; - } - - resolve(arrayBuffer); - }); - - spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue(promise); + spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.callFake(() => + rejectPromise ? Promise.reject(new Error()) : Promise.resolve(arrayBuffer) + ); const gltfJsonLoader = new GltfJsonLoader({ resourceCache: ResourceCache, @@ -985,12 +934,11 @@ describe("Scene/GltfJsonLoader", function () { expect(gltfJsonLoader.gltf).not.toBeDefined(); - gltfJsonLoader.load(); + const promise = gltfJsonLoader.load(); gltfJsonLoader.destroy(); - return gltfJsonLoader.promise.then(function () { - expect(gltfJsonLoader.gltf).not.toBeDefined(); - expect(gltfJsonLoader.isDestroyed()).toBe(true); - }); + await expectAsync(promise).toBeResolved(); + expect(gltfJsonLoader.gltf).not.toBeDefined(); + expect(gltfJsonLoader.isDestroyed()).toBe(true); } it("handles resolving glTF after destroy", function () { @@ -1001,7 +949,7 @@ describe("Scene/GltfJsonLoader", function () { return resolvesGltfAfterDestroy(true); }); - function resolvesProcessedGltfAfterDestroy(rejectPromise) { + async function resolvesProcessedGltfAfterDestroy(rejectPromise) { spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( Promise.resolve(generateJsonBuffer(gltf2).buffer) ); @@ -1027,10 +975,9 @@ describe("Scene/GltfJsonLoader", function () { const promise = gltfJsonLoader.load(); gltfJsonLoader.destroy(); - return promise.finally(function () { - expect(gltfJsonLoader.gltf).not.toBeDefined(); - expect(gltfJsonLoader.isDestroyed()).toBe(true); - }); + await expectAsync(promise).toBeResolved(); + expect(gltfJsonLoader.gltf).not.toBeDefined(); + expect(gltfJsonLoader.isDestroyed()).toBe(true); } it("handles resolving processed glTF after destroy", function () { @@ -1041,7 +988,7 @@ describe("Scene/GltfJsonLoader", function () { return resolvesProcessedGltfAfterDestroy(true); }); - function resolvesTypedArrayAfterDestroy(rejectPromise) { + async function resolvesTypedArrayAfterDestroy(rejectPromise) { const typedArray = generateJsonBuffer(gltf1); const gltfJsonLoader = new GltfJsonLoader({ @@ -1064,12 +1011,11 @@ describe("Scene/GltfJsonLoader", function () { }); expect(gltfJsonLoader.gltf).not.toBeDefined(); - gltfJsonLoader.load(); + const promise = gltfJsonLoader.load(); gltfJsonLoader.destroy(); - return gltfJsonLoader.promise.then(function () { - expect(gltfJsonLoader.gltf).not.toBeDefined(); - expect(gltfJsonLoader.isDestroyed()).toBe(true); - }); + await expectAsync(promise).toBeResolved(); + expect(gltfJsonLoader.gltf).not.toBeDefined(); + expect(gltfJsonLoader.isDestroyed()).toBe(true); } it("handles resolving typed array after destroy", function () { diff --git a/packages/engine/Specs/Scene/GltfLoaderSpec.js b/packages/engine/Specs/Scene/GltfLoaderSpec.js index 4ab49554fd17..3a4f36ec7f3b 100644 --- a/packages/engine/Specs/Scene/GltfLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfLoaderSpec.js @@ -154,19 +154,68 @@ describe( }).toThrowDeveloperError(); }); - it("throws if an unsupported extension is required", function () { + it("load throws if glTF JSON fails to load", async function () { + const error = new Error("404 Not Found"); + spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( + Promise.reject(error) + ); + + const gltfResource = new Resource({ + url: "https://example.com/model.glb", + }); + + const gltfLoader = new GltfLoader({ + gltfResource: gltfResource, + releaseGltfJson: true, + }); + gltfLoaders.push(gltfLoader); + + await expectAsync(gltfLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load glTF\nFailed to load glTF: https://example.com/model.glb\n404 Not Found" + ); + }); + + // This cannot actually ever happen due to gltfPipeline's updateVersion + xit("load throws if the gltf specifies an unknown version", async function () { + spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( + Promise.resolve( + generateJsonBuffer({ + asset: { + version: "3.0", + }, + }).buffer + ) + ); + + const gltfResource = new Resource({ + url: "https://example.com/model.glb", + }); + + const gltfLoader = new GltfLoader({ + gltfResource: gltfResource, + releaseGltfJson: true, + }); + gltfLoaders.push(gltfLoader); + + await expectAsync(gltfLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load glTF\nUnsupported glTF version: 0.1" + ); + }); + + it("load throws if an unsupported extension is required", async function () { function modifyGltf(gltf) { gltf.extensionsRequired = ["NOT_supported_extension"]; return gltf; } - return loadModifiedGltfAndTest(boxTextured, undefined, modifyGltf) - .then(function () { - fail(); - }) - .catch(function (error) { - expect(error).toBeInstanceOf(RuntimeError); - }); + await expectAsync( + loadModifiedGltfAndTest(boxTextured, undefined, modifyGltf) + ).toBeRejectedWithError( + RuntimeError, + "Failed to load glTF\nUnsupported glTF Extension: NOT_supported_extension" + ); }); function getOptions(gltfPath, options) { @@ -180,51 +229,53 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); const targetScene = defaultValue(options.scene, scene); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, targetScene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, targetScene); + return gltfLoader; } - function loadGltfFromJson(gltfPath, options) { - return Resource.fetchJson({ + async function loadGltfFromJson(gltfPath, options) { + const gltf = await Resource.fetchJson({ url: gltfPath, - }).then(function (gltf) { - const loaderOptions = combine(options, { - gltf: gltf, - gltfResource: new Resource({ - url: gltfPath, - }), - incrementallyLoadTextures: false, - }); - const gltfLoader = new GltfLoader(loaderOptions); - gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); }); + const loaderOptions = combine(options, { + gltf: gltf, + gltfResource: new Resource({ + url: gltfPath, + }), + incrementallyLoadTextures: false, + }); + const gltfLoader = new GltfLoader(loaderOptions); + gltfLoaders.push(gltfLoader); + + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } - function loadModifiedGltfAndTest(gltfPath, options, modifyFunction) { - return Resource.fetchJson({ + async function loadModifiedGltfAndTest(gltfPath, options, modifyFunction) { + let gltf = await Resource.fetchJson({ url: gltfPath, - }).then(function (gltf) { - gltf = modifyFunction(gltf); + }); - spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( - Promise.resolve(generateJsonBuffer(gltf).buffer) - ); + gltf = modifyFunction(gltf); - const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); - gltfLoaders.push(gltfLoader); - gltfLoader.load(); + spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( + Promise.resolve(generateJsonBuffer(gltf).buffer) + ); - return waitForLoaderProcess(gltfLoader, scene); - }); + const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); + gltfLoaders.push(gltfLoader); + + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + + return gltfLoader; } function getAttribute(attributes, semantic, setIndex) { @@ -252,13 +303,14 @@ describe( return undefined; } - it("preserves query string in url", function () { + it("preserves query string in url", async function () { const params = "?param1=1¶m2=2"; const url = boxTextured + params; - return loadGltf(url).then(function (gltfLoader) { - const loaderResource = gltfLoader._gltfResource; - expect(loaderResource.url).toEndWith(params); - }); + const gltfLoader = new GltfLoader(getOptions(url)); + gltfLoaders.push(gltfLoader); + await gltfLoader.load(); + const loaderResource = gltfLoader._gltfResource; + expect(loaderResource.url).toEndWith(params); }); it("releases GLB typed array when finished loading", function () { @@ -3207,43 +3259,39 @@ describe( }); }); - it("resolves before textures are loaded when incrementallyLoadTextures is true", function () { + it("becomes ready before textures are loaded when incrementallyLoadTextures is true", async function () { const textureCreate = spyOn(Texture, "create").and.callThrough(); const options = { incrementallyLoadTextures: true, }; - const promise = loadGltf(boxTextured, options); - spyOn(Resource.prototype, "fetchImage").and.returnValue( - promise.then(function () { - const image = new Image(); - image.src = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII="; - - return image; - }) - ); - return promise.then(function (gltfLoader) { - expect(textureCreate).not.toHaveBeenCalled(); - - // Continue processing to load in textures - return pollToPromise(function () { - loaderProcess(gltfLoader, scene); - return gltfLoader._textureLoaders.every(function (loader) { - return ( - loader._state === ResourceLoaderState.READY || - loader._state === ResourceLoaderState.FAILED - ); - }); - }) - .then(function () { - return gltfLoader.texturesLoadedPromise; - }) - .then(function () { - expect(textureCreate).toHaveBeenCalled(); - }); + let resolver; + const promise = new Promise((resolve) => { + resolver = resolve; }); + spyOn(Resource.prototype, "fetchImage").and.returnValue(promise); + + const gltfLoader = await loadGltf(boxTextured, options); + expect(textureCreate).not.toHaveBeenCalled(); + + const image = new Image(); + image.src = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII="; + resolver(image); + + // Continue processing to load in textures + await pollToPromise(function () { + loaderProcess(gltfLoader, scene); + return gltfLoader._textureLoaders.every(function (loader) { + return ( + loader._state === ResourceLoaderState.READY || + loader._state === ResourceLoaderState.FAILED + ); + }); + }); + + expect(textureCreate).toHaveBeenCalled(); }); it("sets default transform", function () { @@ -3288,35 +3336,8 @@ describe( }); }); - it("rejects promise if glTF JSON fails to load", function () { - const error = new Error("404 Not Found"); - spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( - Promise.reject(error) - ); - - const gltfResource = new Resource({ - url: "https://example.com/model.glb", - }); - - const gltfLoader = new GltfLoader({ - gltfResource: gltfResource, - releaseGltfJson: true, - }); - - gltfLoader.load(); - - return gltfLoader.promise - .then(function () { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load glTF\nFailed to load glTF: https://example.com/model.glb\n404 Not Found" - ); - }); - }); - - it("rejects promises if resource fails to load", function () { + // TODO + xit("process throws if image resource fails to load", function () { spyOn(Resource.prototype, "fetchImage").and.callFake(function () { const error = new Error("404 Not Found"); return Promise.reject(error); @@ -3395,11 +3416,6 @@ describe( url: gltfUri, }); - const gltfJsonLoaderCopy = ResourceCache.loadGltfJson({ - gltfResource: gltfResource, - baseResource: gltfResource, - }); - const gltfLoader = new GltfLoader({ gltfResource: gltfResource, }); @@ -3412,8 +3428,6 @@ describe( return promise.then(function () { expect(gltfLoader.components).not.toBeDefined(); expect(gltfLoader.isDestroyed()).toBe(true); - - ResourceCache.unload(gltfJsonLoaderCopy); }); } @@ -4008,32 +4022,30 @@ describe( }); }); - it("throws when loading instanced model for classification", function () { + it("throws when loading instanced model for classification", async function () { const options = { loadForClassification: true, }; - return loadGltf(boxInstanced, options) - .then(function () { - fail(); - }) - .catch(function (error) { - expect(error).toBeInstanceOf(RuntimeError); - }); + await expectAsync( + loadGltf(boxInstanced, options) + ).toBeRejectedWithError( + RuntimeError, + "Failed to load glTF\nModels with the EXT_mesh_gpu_instancing extension cannot be used for classification." + ); }); - it("throws when loading non-triangle mesh for classification", function () { + it("throws when loading non-triangle mesh for classification", async function () { const options = { loadForClassification: true, }; - return loadGltf(pointCloudWithPropertyAttributes, options) - .then(function () { - fail(); - }) - .catch(function (error) { - expect(error).toBeInstanceOf(RuntimeError); - }); + await expectAsync( + loadGltf(pointCloudWithPropertyAttributes, options) + ).toBeRejectedWithError( + RuntimeError, + "Failed to load glTF\nOnly triangle meshes can be used for classification." + ); }); }); diff --git a/packages/engine/Specs/Scene/GltfStructuralMetadataLoaderSpec.js b/packages/engine/Specs/Scene/GltfStructuralMetadataLoaderSpec.js index be4790ebe6f8..6901bbdf1657 100644 --- a/packages/engine/Specs/Scene/GltfStructuralMetadataLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfStructuralMetadataLoaderSpec.js @@ -6,6 +6,7 @@ import { MetadataSchemaLoader, Resource, ResourceCache, + RuntimeError, SupportedImageFormats, } from "../../index.js"; import createScene from "../../../../Specs/createScene.js"; @@ -250,7 +251,7 @@ describe( }).toThrowDeveloperError(); }); - it("rejects promise if buffer view fails to load", function () { + it("load throws if buffer view fails to load", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { const error = new Error("404 Not Found"); return Promise.reject(error); @@ -269,20 +270,13 @@ describe( frameState: mockFrameState, }); - structuralMetadataLoader.load(); - - return structuralMetadataLoader.promise - .then(function (structuralMetadataLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load structural metadata\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" - ); - }); + await expectAsync(structuralMetadataLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load structural metadata\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" + ); }); - it("rejects promise if texture fails to load", function () { + it("load throws if texture fails to load", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(buffer) ); @@ -301,20 +295,13 @@ describe( frameState: mockFrameState, }); - structuralMetadataLoader.load(); - - return structuralMetadataLoader.promise - .then(function (structuralMetadataLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load structural metadata\nFailed to load texture\nFailed to load image: map.png\n404 Not Found" - ); - }); + await expectAsync(structuralMetadataLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load structural metadata\nFailed to load texture\nFailed to load image: map.png\n404 Not Found" + ); }); - it("rejects promise if external schema fails to load", function () { + it("load throws if external schema fails to load", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(buffer) ); @@ -337,20 +324,13 @@ describe( frameState: mockFrameState, }); - structuralMetadataLoader.load(); - - return structuralMetadataLoader.promise - .then(function () { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load structural metadata\nFailed to load schema: https://example.com/schema.json\n404 Not Found" - ); - }); + await expectAsync(structuralMetadataLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load structural metadata\nFailed to load schema: https://example.com/schema.json\n404 Not Found" + ); }); - it("loads structural metadata", function () { + it("loads structural metadata", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(buffer) ); @@ -368,56 +348,56 @@ describe( frameState: mockFrameState, }); - structuralMetadataLoader.load(); - - return waitForLoaderProcess(structuralMetadataLoader, scene).then( - function (structuralMetadataLoader) { - loaderProcess(structuralMetadataLoader, scene); // Check that calling process after load doesn't break anything - - const structuralMetadata = - structuralMetadataLoader.structuralMetadata; - const buildingsTable = structuralMetadata.getPropertyTable(0); - expect(buildingsTable.id).toBe(0); - const treesTable = structuralMetadata.getPropertyTable(1); - expect(treesTable.id).toBe(1); - - const mapTexture = structuralMetadata.getPropertyTexture(0); - expect(mapTexture.id).toBe(0); - const orthoTexture = structuralMetadata.getPropertyTexture(1); - expect(orthoTexture.id).toBe(1); - - expect(buildingsTable.getProperty(0, "name")).toBe("House"); - expect(buildingsTable.getProperty(1, "name")).toBe("Hospital"); - expect(treesTable.getProperty(0, "species")).toEqual([ - "Sparrow", - "Squirrel", - ]); - expect(treesTable.getProperty(1, "species")).toEqual(["Crow"]); - - const colorProperty = mapTexture.getProperty("color"); - const intensityProperty = mapTexture.getProperty("intensity"); - const vegetationProperty = orthoTexture.getProperty("vegetation"); - - expect(colorProperty.textureReader.texture.width).toBe(1); - expect(colorProperty.textureReader.texture.height).toBe(1); - expect(colorProperty.textureReader.texture).toBe( - intensityProperty.textureReader.texture - ); - - expect(vegetationProperty.textureReader.texture.width).toBe(1); - expect(vegetationProperty.textureReader.texture.height).toBe(1); - expect(vegetationProperty.textureReader.texture).not.toBe( - colorProperty.textureReader.texture - ); - - expect( - Object.keys(structuralMetadata.schema.classes).sort() - ).toEqual(["building", "map", "ortho", "tree"]); - } + await structuralMetadataLoader.load(); + await waitForLoaderProcess(structuralMetadataLoader, scene); + expect(() => + loaderProcess(structuralMetadataLoader, scene) + ).not.toThrow(); + + const structuralMetadata = structuralMetadataLoader.structuralMetadata; + const buildingsTable = structuralMetadata.getPropertyTable(0); + expect(buildingsTable.id).toBe(0); + const treesTable = structuralMetadata.getPropertyTable(1); + expect(treesTable.id).toBe(1); + + const mapTexture = structuralMetadata.getPropertyTexture(0); + expect(mapTexture.id).toBe(0); + const orthoTexture = structuralMetadata.getPropertyTexture(1); + expect(orthoTexture.id).toBe(1); + + expect(buildingsTable.getProperty(0, "name")).toBe("House"); + expect(buildingsTable.getProperty(1, "name")).toBe("Hospital"); + expect(treesTable.getProperty(0, "species")).toEqual([ + "Sparrow", + "Squirrel", + ]); + expect(treesTable.getProperty(1, "species")).toEqual(["Crow"]); + + const colorProperty = mapTexture.getProperty("color"); + const intensityProperty = mapTexture.getProperty("intensity"); + const vegetationProperty = orthoTexture.getProperty("vegetation"); + + expect(colorProperty.textureReader.texture.width).toBe(1); + expect(colorProperty.textureReader.texture.height).toBe(1); + expect(colorProperty.textureReader.texture).toBe( + intensityProperty.textureReader.texture + ); + + expect(vegetationProperty.textureReader.texture.width).toBe(1); + expect(vegetationProperty.textureReader.texture.height).toBe(1); + expect(vegetationProperty.textureReader.texture).not.toBe( + colorProperty.textureReader.texture ); + + expect(Object.keys(structuralMetadata.schema.classes).sort()).toEqual([ + "building", + "map", + "ortho", + "tree", + ]); }); - it("loads structural metadata with external schema", function () { + it("loads structural metadata with external schema", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(buffer) ); @@ -439,20 +419,19 @@ describe( frameState: mockFrameState, }); - structuralMetadataLoader.load(); + await structuralMetadataLoader.load(); + await waitForLoaderProcess(structuralMetadataLoader, scene); - return waitForLoaderProcess(structuralMetadataLoader, scene).then( - function (structuralMetadataLoader) { - const structuralMetadata = - structuralMetadataLoader.structuralMetadata; - expect( - Object.keys(structuralMetadata.schema.classes).sort() - ).toEqual(["building", "map", "ortho", "tree"]); - } - ); + const structuralMetadata = structuralMetadataLoader.structuralMetadata; + expect(Object.keys(structuralMetadata.schema.classes).sort()).toEqual([ + "building", + "map", + "ortho", + "tree", + ]); }); - it("destroys structural metadata", function () { + it("destroys structural metadata", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(buffer) ); @@ -489,46 +468,34 @@ describe( frameState: mockFrameState, }); - structuralMetadataLoader.load(); + await structuralMetadataLoader.load(); - return waitForLoaderProcess(structuralMetadataLoader, scene).then( - function (structuralMetadataLoader) { - expect(structuralMetadataLoader.structuralMetadata).toBeDefined(); - expect(structuralMetadataLoader.isDestroyed()).toBe(false); + await waitForLoaderProcess(structuralMetadataLoader, scene); + expect(structuralMetadataLoader.structuralMetadata).toBeDefined(); + expect(structuralMetadataLoader.isDestroyed()).toBe(false); - structuralMetadataLoader.destroy(); + structuralMetadataLoader.destroy(); - expect(structuralMetadataLoader.structuralMetadata).not.toBeDefined(); - expect(structuralMetadataLoader.isDestroyed()).toBe(true); + expect(structuralMetadataLoader.structuralMetadata).not.toBeDefined(); + expect(structuralMetadataLoader.isDestroyed()).toBe(true); - expect(destroyBufferView.calls.count()).toBe(6); - expect(destroyTexture.calls.count()).toBe(2); - expect(destroySchema.calls.count()).toBe(1); - } - ); + expect(destroyBufferView.calls.count()).toBe(6); + expect(destroyTexture.calls.count()).toBe(2); + expect(destroySchema.calls.count()).toBe(1); }); - function resolveAfterDestroy(rejectPromise) { + async function resolveAfterDestroy(rejectPromise) { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(buffer) ); spyOn(Resource.prototype, "fetchImage").and.returnValue( Promise.resolve(image) ); - - let promise = new Promise(function (resolve, reject) { - if (rejectPromise) { - const error = new Error("404 Not Found"); - reject(error); - return; - } - resolve(schemaJson); - }); - if (rejectPromise) { - // handle the error so Jasmine doesn't fail the test - promise = promise.catch(function () {}); - } - spyOn(Resource.prototype, "fetchJson").and.returnValue(promise); + spyOn(Resource.prototype, "fetchJson").and.callFake(() => + rejectPromise + ? Promise.reject(new Error("")) + : Promise.resolve(schemaJson) + ); const destroyBufferView = spyOn( GltfBufferViewLoader.prototype, @@ -543,60 +510,25 @@ describe( "destroy" ).and.callThrough(); - // Load a copy of structural metadata into the cache so that the resource - // promises resolve even if the structural metadata loader is destroyed - const structuralMetadataLoaderCopy = new GltfStructuralMetadataLoader({ - gltf: gltf, - extension: extension, + const structuralMetadataLoader = new GltfStructuralMetadataLoader({ + gltf: gltfSchemaUri, + extension: extensionSchemaUri, gltfResource: gltfResource, baseResource: gltfResource, supportedImageFormats: new SupportedImageFormats(), frameState: mockFrameState, }); - // Also load a copy of the schema into the cache - const schemaResource = gltfResource.getDerivedResource({ - url: "schema.json", - }); - const schemaCopy = ResourceCache.loadSchema({ - resource: schemaResource, - }); + expect(structuralMetadataLoader.structuralMetadata).not.toBeDefined(); + const promise = structuralMetadataLoader.load(); + structuralMetadataLoader.destroy(); - if (rejectPromise) { - // handle the error so Jasmine doesn't fail the test - schemaCopy.promise.catch(function () {}); - } - - structuralMetadataLoaderCopy.load(); - - return waitForLoaderProcess(structuralMetadataLoaderCopy, scene).then( - function (structuralMetadataLoaderCopy) { - // Ignore structuralMetadataLoaderCopy destroying its buffer views - destroyBufferView.calls.reset(); - - const structuralMetadataLoader = new GltfStructuralMetadataLoader({ - gltf: gltfSchemaUri, - extension: extensionSchemaUri, - gltfResource: gltfResource, - baseResource: gltfResource, - supportedImageFormats: new SupportedImageFormats(), - frameState: mockFrameState, - }); - expect(structuralMetadataLoader.structuralMetadata).not.toBeDefined(); - structuralMetadataLoader.load(); - structuralMetadataLoader.destroy(); - - expect(structuralMetadataLoader.structuralMetadata).not.toBeDefined(); - expect(structuralMetadataLoader.isDestroyed()).toBe(true); - - structuralMetadataLoaderCopy.destroy(); - - expect(destroyBufferView.calls.count()).toBe(6); - expect(destroyTexture.calls.count()).toBe(2); - expect(destroySchema.calls.count()).toBe(1); - - ResourceCache.unload(schemaCopy); - } - ); + expect(structuralMetadataLoader.structuralMetadata).not.toBeDefined(); + expect(structuralMetadataLoader.isDestroyed()).toBe(true); + + expect(destroyBufferView.calls.count()).toBe(6); + expect(destroyTexture.calls.count()).toBe(2); + expect(destroySchema.calls.count()).toBe(1); + await expectAsync(promise).toBeResolved(); } it("handles resolving resources after destroy", function () { diff --git a/packages/engine/Specs/Scene/GltfTextureLoaderSpec.js b/packages/engine/Specs/Scene/GltfTextureLoaderSpec.js index 3ce021331954..0845aca115ab 100644 --- a/packages/engine/Specs/Scene/GltfTextureLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfTextureLoaderSpec.js @@ -6,6 +6,7 @@ import { JobScheduler, Resource, ResourceCache, + RuntimeError, SupportedImageFormats, Texture, TextureMinificationFilter, @@ -232,10 +233,9 @@ describe( }).toThrowDeveloperError(); }); - it("rejects promise if image fails to load", function () { - const error = new Error("404 Not Found"); - spyOn(Resource.prototype, "fetchImage").and.returnValue( - Promise.reject(error) + it("load throws if image fails to load", async function () { + spyOn(Resource.prototype, "fetchImage").and.callFake(() => + Promise.reject(new Error("404 Not Found")) ); const textureLoader = new GltfTextureLoader({ @@ -247,20 +247,13 @@ describe( supportedImageFormats: new SupportedImageFormats(), }); - textureLoader.load(); - - return textureLoader.promise - .then(function (textureLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load texture\nFailed to load image: image.png\n404 Not Found" - ); - }); + await expectAsync(textureLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load texture\nFailed to load image: image.png\n404 Not Found" + ); }); - it("loads texture", function () { + it("loads texture", async function () { spyOn(Resource.prototype, "fetchImage").and.returnValue( Promise.resolve(image) ); @@ -289,20 +282,20 @@ describe( supportedImageFormats: new SupportedImageFormats(), }); - loaderProcess(textureLoader, scene); // Check that calling process before load doesn't break anything + expect(() => loaderProcess(textureLoader, scene)).not.toThrowError(); - textureLoader.load(); + await textureLoader.load(); + await waitForLoaderProcess(textureLoader, scene); - return waitForLoaderProcess(textureLoader, scene).then(function ( - textureLoader - ) { - loaderProcess(textureLoader, scene); // Check that calling process after load doesn't break anything - expect(textureLoader.texture.width).toBe(1); - expect(textureLoader.texture.height).toBe(1); - }); + expect(() => loaderProcess(textureLoader, scene)).not.toThrowError(); + expect(textureLoader.texture.width).toBe(1); + expect(textureLoader.texture.height).toBe(1); + expect(ResourceCache.statistics.texturesByteLength).toBe( + textureLoader.texture.sizeInBytes + ); }); - it("creates texture synchronously", function () { + it("creates texture synchronously", async function () { spyOn(Resource.prototype, "fetchImage").and.returnValue( Promise.resolve(image) ); @@ -317,18 +310,15 @@ describe( asynchronous: false, }); - textureLoader.load(); + await textureLoader.load(); + await waitForLoaderProcess(textureLoader, scene); - return waitForLoaderProcess(textureLoader, scene).then(function ( - textureLoader - ) { - loaderProcess(textureLoader, scene); // Check that calling process after load doesn't break anything - expect(textureLoader.texture.width).toBe(1); - expect(textureLoader.texture.height).toBe(1); - }); + expect(() => loaderProcess(textureLoader, scene)).not.toThrowError(); + expect(textureLoader.texture.width).toBe(1); + expect(textureLoader.texture.height).toBe(1); }); - it("loads KTX2/Basis texture", function () { + it("loads KTX2/Basis texture", async function () { if (!scene.context.supportsBasis) { return; } @@ -347,18 +337,15 @@ describe( }), }); - textureLoader.load(); + await textureLoader.load(); + await waitForLoaderProcess(textureLoader, scene); - return waitForLoaderProcess(textureLoader, scene).then(function ( - textureLoader - ) { - expect(textureLoader.texture.width).toBe(4); - expect(textureLoader.texture.height).toBe(4); - expect(gl.compressedTexImage2D.calls.count()).toEqual(1); - }); + expect(textureLoader.texture.width).toBe(4); + expect(textureLoader.texture.height).toBe(4); + expect(gl.compressedTexImage2D.calls.count()).toEqual(1); }); - it("loads KTX2/Basis texture with mipmap", function () { + it("loads KTX2/Basis texture with mipmap", async function () { if (!scene.context.supportsBasis) { return; } @@ -377,18 +364,15 @@ describe( }), }); - textureLoader.load(); + await textureLoader.load(); + await waitForLoaderProcess(textureLoader, scene); - return waitForLoaderProcess(textureLoader, scene).then(function ( - textureLoader - ) { - expect(textureLoader.texture.width).toBe(4); - expect(textureLoader.texture.height).toBe(4); - expect(gl.compressedTexImage2D.calls.count()).toEqual(3); - }); + expect(textureLoader.texture.width).toBe(4); + expect(textureLoader.texture.height).toBe(4); + expect(gl.compressedTexImage2D.calls.count()).toEqual(3); }); - it("loads KTX2/Basis texture with incompatible mipmap sampler", function () { + it("loads KTX2/Basis texture with incompatible mipmap sampler", async function () { if (!scene.context.supportsBasis) { return; } @@ -411,23 +395,20 @@ describe( asynchronous: false, }); - textureLoader.load(); + await textureLoader.load(); + await waitForLoaderProcess(textureLoader, scene); - return waitForLoaderProcess(textureLoader, scene).then(function ( - textureLoader - ) { - expect(GltfLoaderUtil.createSampler).toHaveBeenCalledWith({ - gltf: gltfKtx2MissingMipmap, - textureInfo: gltf.materials[0].emissiveTexture, - compressedTextureNoMipmap: true, - }); - expect(textureLoader.texture.sampler.minificationFilter).toBe( - TextureMinificationFilter.NEAREST - ); + expect(GltfLoaderUtil.createSampler).toHaveBeenCalledWith({ + gltf: gltfKtx2MissingMipmap, + textureInfo: gltf.materials[0].emissiveTexture, + compressedTextureNoMipmap: true, }); + expect(textureLoader.texture.sampler.minificationFilter).toBe( + TextureMinificationFilter.NEAREST + ); }); - it("generates mipmap if sampler requires it", function () { + it("generates mipmap if sampler requires it", async function () { spyOn(Resource.prototype, "fetchImage").and.returnValue( Promise.resolve(image) ); @@ -446,18 +427,15 @@ describe( supportedImageFormats: new SupportedImageFormats(), }); - textureLoader.load(); + await textureLoader.load(); + await waitForLoaderProcess(textureLoader, scene); - return waitForLoaderProcess(textureLoader, scene).then(function ( - textureLoader - ) { - expect(textureLoader.texture.width).toBe(1); - expect(textureLoader.texture.height).toBe(1); - expect(generateMipmap).toHaveBeenCalled(); - }); + expect(textureLoader.texture.width).toBe(1); + expect(textureLoader.texture.height).toBe(1); + expect(generateMipmap).toHaveBeenCalled(); }); - it("generates power-of-two texture if sampler requires it", function () { + it("generates power-of-two texture if sampler requires it", async function () { spyOn(Resource.prototype, "fetchImage").and.returnValue( Promise.resolve(imageNpot) ); @@ -471,17 +449,14 @@ describe( supportedImageFormats: new SupportedImageFormats(), }); - textureLoader.load(); + await textureLoader.load(); + await waitForLoaderProcess(textureLoader, scene); - return waitForLoaderProcess(textureLoader, scene).then(function ( - textureLoader - ) { - expect(textureLoader.texture.width).toBe(4); - expect(textureLoader.texture.height).toBe(2); - }); + expect(textureLoader.texture.width).toBe(4); + expect(textureLoader.texture.height).toBe(2); }); - it("does not generate power-of-two texture if sampler does not require it", function () { + it("does not generate power-of-two texture if sampler does not require it", async function () { spyOn(Resource.prototype, "fetchImage").and.returnValue( Promise.resolve(imageNpot) ); @@ -495,17 +470,14 @@ describe( supportedImageFormats: new SupportedImageFormats(), }); - textureLoader.load(); + await textureLoader.load(); + await waitForLoaderProcess(textureLoader, scene); - return waitForLoaderProcess(textureLoader, scene).then(function ( - textureLoader - ) { - expect(textureLoader.texture.width).toBe(3); - expect(textureLoader.texture.height).toBe(2); - }); + expect(textureLoader.texture.width).toBe(3); + expect(textureLoader.texture.height).toBe(2); }); - it("destroys texture loader", function () { + it("destroys texture loader", async function () { spyOn(Resource.prototype, "fetchImage").and.returnValue( Promise.resolve(image) ); @@ -531,41 +503,30 @@ describe( expect(textureLoader.texture).not.toBeDefined(); - textureLoader.load(); - - return waitForLoaderProcess(textureLoader, scene).then(function ( - textureLoader - ) { - expect(textureLoader.texture).toBeDefined(); - expect(textureLoader.isDestroyed()).toBe(false); + await textureLoader.load(); + await waitForLoaderProcess(textureLoader, scene); + expect(textureLoader.texture).toBeDefined(); + expect(textureLoader.isDestroyed()).toBe(false); - textureLoader.destroy(); + textureLoader.destroy(); - expect(textureLoader.texture).not.toBeDefined(); - expect(textureLoader.isDestroyed()).toBe(true); - expect(unloadImage).toHaveBeenCalled(); - expect(destroyTexture).toHaveBeenCalled(); - }); + expect(textureLoader.texture).not.toBeDefined(); + expect(textureLoader.isDestroyed()).toBe(true); + expect(unloadImage).toHaveBeenCalled(); + expect(destroyTexture).toHaveBeenCalled(); }); - function resolveImageAfterDestroy(rejectPromise) { - const promise = new Promise(function (resolve, reject) { - if (rejectPromise) { - reject(new Error()); - } else { - resolve(image); - } - }); - spyOn(Resource.prototype, "fetchImage").and.returnValue(promise); - - // Load a copy of the image into the cache so that the image - // promise resolves even if the texture loader is destroyed - const imageLoaderCopy = ResourceCache.loadImage({ - gltf: gltf, - imageId: 0, - gltfResource: gltfResource, - baseResource: gltfResource, - }); + async function resolveImageAfterDestroy(rejectPromise) { + spyOn(Resource.prototype, "fetchImage").and.callFake( + () => + new Promise(function (resolve, reject) { + if (rejectPromise) { + reject(new Error()); + } else { + resolve(image); + } + }) + ); const textureLoader = new GltfTextureLoader({ resourceCache: ResourceCache, @@ -578,15 +539,12 @@ describe( expect(textureLoader.texture).not.toBeDefined(); - textureLoader.load(); + const promise = textureLoader.load(); textureLoader.destroy(); - return textureLoader.promise.then(function () { - expect(textureLoader.texture).not.toBeDefined(); - expect(textureLoader.isDestroyed()).toBe(true); - - ResourceCache.unload(imageLoaderCopy); - }); + await expectAsync(promise).toBeResolved(); + expect(textureLoader.texture).not.toBeDefined(); + expect(textureLoader.isDestroyed()).toBe(true); } it("handles resolving image after destroy", function () { diff --git a/packages/engine/Specs/Scene/GltfVertexBufferLoaderSpec.js b/packages/engine/Specs/Scene/GltfVertexBufferLoaderSpec.js index b09b37832bef..8351e05d96dc 100644 --- a/packages/engine/Specs/Scene/GltfVertexBufferLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfVertexBufferLoaderSpec.js @@ -9,6 +9,7 @@ import { JobScheduler, Resource, ResourceCache, + RuntimeError, } from "../../index.js"; import concatTypedArrays from "../../../../Specs/concatTypedArrays.js"; import createScene from "../../../../Specs/createScene.js"; @@ -351,10 +352,9 @@ describe( }).toThrowDeveloperError(); }); - it("rejects promise if buffer view fails to load", function () { - const error = new Error("404 Not Found"); - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.reject(error) + it("load throws if buffer view fails to load", async function () { + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(() => + Promise.reject(new Error("404 Not Found")) ); const vertexBufferLoader = new GltfVertexBufferLoader({ @@ -366,20 +366,13 @@ describe( loadBuffer: true, }); - vertexBufferLoader.load(); - - return vertexBufferLoader.promise - .then(function (vertexBufferLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load vertex buffer\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" - ); - }); + await expectAsync(vertexBufferLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load vertex buffer\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found" + ); }); - it("rejects promise if draco fails to load", function () { + it("process throws if draco fails to load", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(dracoArrayBuffer) ); @@ -400,20 +393,16 @@ describe( loadBuffer: true, }); - vertexBufferLoader.load(); - - return waitForLoaderProcess(vertexBufferLoader, scene) - .then(function (vertexBufferLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load vertex buffer\nFailed to load Draco\nDraco decode failed" - ); - }); + await vertexBufferLoader.load(); + await expectAsync( + waitForLoaderProcess(vertexBufferLoader, scene) + ).toBeRejectedWithError( + RuntimeError, + "Failed to load vertex buffer\nFailed to load Draco\nDraco decode failed" + ); }); - it("loads as buffer", function () { + it("loads as buffer", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -443,20 +432,18 @@ describe( loadBuffer: true, }); - vertexBufferLoader.load(); + await vertexBufferLoader.load(); + await waitForLoaderProcess(vertexBufferLoader, scene); - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - loaderProcess(vertexBufferLoader, scene); // Check that calling process after load doesn't break anything - expect(vertexBufferLoader.buffer.sizeInBytes).toBe( - positions.byteLength - ); - expect(vertexBufferLoader.typedArray).toBeUndefined(); - }); + expect(() => loaderProcess(vertexBufferLoader, scene)).not.toThrowError(); + expect(vertexBufferLoader.buffer.sizeInBytes).toBe(positions.byteLength); + expect(vertexBufferLoader.typedArray).toBeUndefined(); + expect(ResourceCache.statistics.geometryByteLength).toBe( + vertexBufferLoader.buffer.sizeInBytes + ); }); - it("loads as typed array", function () { + it("loads as typed array", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -473,20 +460,22 @@ describe( loadTypedArray: true, }); - vertexBufferLoader.load(); + await vertexBufferLoader.load(); + await waitForLoaderProcess(vertexBufferLoader, scene); - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - expect(vertexBufferLoader.typedArray.byteLength).toBe( - positions.byteLength - ); - expect(vertexBufferLoader.buffer).toBeUndefined(); - expect(Buffer.createVertexBuffer.calls.count()).toBe(0); - }); + expect(() => loaderProcess(vertexBufferLoader, scene)).not.toThrowError(); + expect(vertexBufferLoader.typedArray.byteLength).toBe( + positions.byteLength + ); + expect(vertexBufferLoader.buffer).toBeUndefined(); + expect(Buffer.createVertexBuffer.calls.count()).toBe(0); + + expect(ResourceCache.statistics.geometryByteLength).toBe( + vertexBufferLoader.typedArray.byteLength + ); }); - it("loads as both buffer and typed array", function () { + it("loads as both buffer and typed array", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -517,22 +506,21 @@ describe( loadTypedArray: true, }); - vertexBufferLoader.load(); + await vertexBufferLoader.load(); + await waitForLoaderProcess(vertexBufferLoader, scene); - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - loaderProcess(vertexBufferLoader, scene); // Check that calling process after load doesn't break anything - expect(vertexBufferLoader.buffer.sizeInBytes).toBe( - positions.byteLength - ); - expect(vertexBufferLoader.typedArray.byteLength).toBe( - positions.byteLength - ); - }); + expect(() => loaderProcess(vertexBufferLoader, scene)).not.toThrowError(); + expect(vertexBufferLoader.buffer.sizeInBytes).toBe(positions.byteLength); + expect(vertexBufferLoader.typedArray.byteLength).toBe( + positions.byteLength + ); + const totalSize = + vertexBufferLoader.typedArray.byteLength + + vertexBufferLoader.buffer.sizeInBytes; + expect(ResourceCache.statistics.geometryByteLength).toBe(totalSize); }); - it("creates vertex buffer synchronously", function () { + it("creates vertex buffer synchronously", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -548,19 +536,13 @@ describe( loadBuffer: true, }); - vertexBufferLoader.load(); - - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - expect(vertexBufferLoader.buffer.sizeInBytes).toBe( - positions.byteLength - ); - expect(vertexBufferLoader.typedArray).toBeUndefined(); - }); + await vertexBufferLoader.load(); + await waitForLoaderProcess(vertexBufferLoader, scene); + expect(vertexBufferLoader.buffer.sizeInBytes).toBe(positions.byteLength); + expect(vertexBufferLoader.typedArray).toBeUndefined(); }); - it("loads positions from draco", function () { + it("loads positions from draco", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -586,34 +568,34 @@ describe( loadBuffer: true, }); - vertexBufferLoader.load(); + await vertexBufferLoader.load(); + await waitForLoaderProcess(vertexBufferLoader, scene); - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - loaderProcess(vertexBufferLoader, scene); // Check that calling process after load doesn't break anything - expect(vertexBufferLoader.buffer.sizeInBytes).toBe( - decodedPositions.byteLength - ); - expect(vertexBufferLoader.typedArray).toBeUndefined(); - const quantization = vertexBufferLoader.quantization; - expect(quantization.octEncoded).toBe(false); - expect(quantization.quantizedVolumeOffset).toEqual( - new Cartesian3(-1.0, -1.0, -1.0) - ); - expect(quantization.quantizedVolumeDimensions).toEqual( - new Cartesian3(2.0, 2.0, 2.0) - ); - expect(quantization.normalizationRange).toEqual( - new Cartesian3(16383, 16383, 16383) - ); - expect(quantization.componentDatatype).toBe( - ComponentDatatype.UNSIGNED_SHORT - ); - }); + expect(() => loaderProcess(vertexBufferLoader, scene)).not.toThrowError(); + expect(vertexBufferLoader.buffer.sizeInBytes).toBe( + decodedPositions.byteLength + ); + expect(vertexBufferLoader.typedArray).toBeUndefined(); + const quantization = vertexBufferLoader.quantization; + expect(quantization.octEncoded).toBe(false); + expect(quantization.quantizedVolumeOffset).toEqual( + new Cartesian3(-1.0, -1.0, -1.0) + ); + expect(quantization.quantizedVolumeDimensions).toEqual( + new Cartesian3(2.0, 2.0, 2.0) + ); + expect(quantization.normalizationRange).toEqual( + new Cartesian3(16383, 16383, 16383) + ); + expect(quantization.componentDatatype).toBe( + ComponentDatatype.UNSIGNED_SHORT + ); + expect(ResourceCache.statistics.geometryByteLength).toBe( + vertexBufferLoader.buffer.sizeInBytes + ); }); - it("loads normals from draco", function () { + it("loads normals from draco", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -633,28 +615,25 @@ describe( loadBuffer: true, }); - vertexBufferLoader.load(); + await vertexBufferLoader.load(); + await waitForLoaderProcess(vertexBufferLoader, scene); - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - expect(vertexBufferLoader.buffer.sizeInBytes).toBe( - decodedNormals.byteLength - ); - - const quantization = vertexBufferLoader.quantization; - expect(quantization.octEncoded).toBe(true); - expect(quantization.octEncodedZXY).toBe(true); - expect(quantization.quantizedVolumeOffset).toBeUndefined(); - expect(quantization.quantizedVolumeDimensions).toBeUndefined(); - expect(quantization.normalizationRange).toBe(1023); - expect(quantization.componentDatatype).toBe( - ComponentDatatype.UNSIGNED_BYTE - ); - }); + expect(vertexBufferLoader.buffer.sizeInBytes).toBe( + decodedNormals.byteLength + ); + + const quantization = vertexBufferLoader.quantization; + expect(quantization.octEncoded).toBe(true); + expect(quantization.octEncodedZXY).toBe(true); + expect(quantization.quantizedVolumeOffset).toBeUndefined(); + expect(quantization.quantizedVolumeDimensions).toBeUndefined(); + expect(quantization.normalizationRange).toBe(1023); + expect(quantization.componentDatatype).toBe( + ComponentDatatype.UNSIGNED_BYTE + ); }); - it("destroys vertex buffer loaded from buffer view", function () { + it("destroys vertex buffer loaded from buffer view", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -679,24 +658,20 @@ describe( loadBuffer: true, }); - vertexBufferLoader.load(); - - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - expect(vertexBufferLoader.buffer).toBeDefined(); - expect(vertexBufferLoader.isDestroyed()).toBe(false); + await vertexBufferLoader.load(); + await waitForLoaderProcess(vertexBufferLoader, scene); + expect(vertexBufferLoader.buffer).toBeDefined(); + expect(vertexBufferLoader.isDestroyed()).toBe(false); - vertexBufferLoader.destroy(); + vertexBufferLoader.destroy(); - expect(vertexBufferLoader.buffer).not.toBeDefined(); - expect(vertexBufferLoader.isDestroyed()).toBe(true); - expect(unloadBufferView).toHaveBeenCalled(); - expect(destroyVertexBuffer).toHaveBeenCalled(); - }); + expect(vertexBufferLoader.buffer).not.toBeDefined(); + expect(vertexBufferLoader.isDestroyed()).toBe(true); + expect(unloadBufferView).toHaveBeenCalled(); + expect(destroyVertexBuffer).toHaveBeenCalled(); }); - it("destroys vertex buffer loaded from draco", function () { + it("destroys vertex buffer loaded from draco", async function () { spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( Promise.resolve(arrayBuffer) ); @@ -726,33 +701,26 @@ describe( loadBuffer: true, }); - vertexBufferLoader.load(); + await vertexBufferLoader.load(); + await waitForLoaderProcess(vertexBufferLoader, scene); - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - expect(vertexBufferLoader.buffer).toBeDefined(); - expect(vertexBufferLoader.isDestroyed()).toBe(false); + expect(vertexBufferLoader.buffer).toBeDefined(); + expect(vertexBufferLoader.isDestroyed()).toBe(false); - vertexBufferLoader.destroy(); + vertexBufferLoader.destroy(); - expect(vertexBufferLoader.buffer).not.toBeDefined(); - expect(vertexBufferLoader.isDestroyed()).toBe(true); - expect(unloadDraco).toHaveBeenCalled(); - expect(destroyVertexBuffer).toHaveBeenCalled(); - }); + expect(vertexBufferLoader.buffer).not.toBeDefined(); + expect(vertexBufferLoader.isDestroyed()).toBe(true); + expect(unloadDraco).toHaveBeenCalled(); + expect(destroyVertexBuffer).toHaveBeenCalled(); }); - function resolveBufferViewAfterDestroy(rejectPromise) { - const promise = new Promise(function (resolve, reject) { - if (rejectPromise) { - reject(new Error()); - } else { - resolve(arrayBuffer); - } - }); - - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(promise); + async function resolveBufferViewAfterDestroy(rejectPromise) { + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(() => + rejectPromise + ? Promise.reject(new Error()) + : Promise.resolve(arrayBuffer) + ); const vertexBufferLoader = new GltfVertexBufferLoader({ resourceCache: ResourceCache, @@ -765,13 +733,13 @@ describe( expect(vertexBufferLoader.buffer).not.toBeDefined(); - vertexBufferLoader.load(); + const promise = vertexBufferLoader.load(); vertexBufferLoader.destroy(); - return vertexBufferLoader.promise.finally(function () { - expect(vertexBufferLoader.buffer).not.toBeDefined(); - expect(vertexBufferLoader.isDestroyed()).toBe(true); - }); + await expectAsync(promise).toBeResolved(); + + expect(vertexBufferLoader.buffer).not.toBeDefined(); + expect(vertexBufferLoader.isDestroyed()).toBe(true); } it("handles resolving buffer view after destroy", function () { @@ -782,7 +750,7 @@ describe( return resolveBufferViewAfterDestroy(true); }); - function resolveDracoAfterDestroy(rejectPromise) { + async function resolveDracoAfterDestroy(rejectPromise) { const vertexBufferLoader = new GltfVertexBufferLoader({ resourceCache: ResourceCache, gltf: gltfDraco, @@ -820,14 +788,14 @@ describe( expect(vertexBufferLoader.buffer).not.toBeDefined(); - vertexBufferLoader.load(); - loaderProcess(vertexBufferLoader, scene); - return vertexBufferLoader.promise.finally(function () { - expect(decodeBufferView).toHaveBeenCalled(); // Make sure the decode actually starts + await vertexBufferLoader.load(); // Destroy happens in mock function above + await expectAsync( + waitForLoaderProcess(vertexBufferLoader, scene) + ).toBeResolved(); - expect(vertexBufferLoader.buffer).not.toBeDefined(); - expect(vertexBufferLoader.isDestroyed()).toBe(true); - }); + expect(decodeBufferView).toHaveBeenCalled(); // Make sure the decode actually starts + expect(vertexBufferLoader.buffer).not.toBeDefined(); + expect(vertexBufferLoader.isDestroyed()).toBe(true); } it("handles resolving draco after destroy", function () { diff --git a/packages/engine/Specs/Scene/MetadataSchemaLoaderSpec.js b/packages/engine/Specs/Scene/MetadataSchemaLoaderSpec.js index 2aab3e9421f6..9c14c187e943 100644 --- a/packages/engine/Specs/Scene/MetadataSchemaLoaderSpec.js +++ b/packages/engine/Specs/Scene/MetadataSchemaLoaderSpec.js @@ -3,6 +3,7 @@ import { ResourceCache, ResourceLoaderState, MetadataSchemaLoader, + RuntimeError, } from "../../index.js"; describe("Scene/MetadataSchemaLoader", function () { @@ -61,7 +62,7 @@ describe("Scene/MetadataSchemaLoader", function () { }).toThrowDeveloperError(); }); - it("rejects promise if schema cannot be fetched", function () { + it("load throws if schema cannot be fetched", async function () { spyOn(Resource.prototype, "fetchJson").and.callFake(function () { const error = new Error("404 Not Found"); return Promise.reject(error); @@ -71,38 +72,29 @@ describe("Scene/MetadataSchemaLoader", function () { resource: resource, }); - schemaLoader.load(); - - return schemaLoader.promise - .then(function (schemaLoader) { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load schema: https://example.com/schema.json\n404 Not Found" - ); - }); + await expectAsync(schemaLoader.load()).toBeRejectedWithError( + RuntimeError, + "Failed to load schema: https://example.com/schema.json\n404 Not Found" + ); }); - it("loads schema from JSON", function () { + it("loads schema from JSON", async function () { const schemaLoader = new MetadataSchemaLoader({ schema: schemaJson, }); - schemaLoader.load(); + await schemaLoader.load(); - return schemaLoader.promise.then(function (schemaLoader) { - const schema = schemaLoader.schema; - expect(schema).toBeDefined(); + const schema = schemaLoader.schema; + expect(schema).toBeDefined(); - const enums = schema.enums; - expect(enums.treeType).toBeDefined(); + const enums = schema.enums; + expect(enums.treeType).toBeDefined(); - const classes = schema.classes; - expect(classes.tree).toBeDefined(); - }); + const classes = schema.classes; + expect(classes.tree).toBeDefined(); }); - it("loads external schema", function () { + it("loads external schema", async function () { const fetchJson = spyOn(Resource.prototype, "fetchJson").and.returnValue( Promise.resolve(schemaJson) ); @@ -111,23 +103,21 @@ describe("Scene/MetadataSchemaLoader", function () { resource: resource, }); - schemaLoader.load(); + await schemaLoader.load(); - return schemaLoader.promise.then(function (schemaLoader) { - expect(fetchJson).toHaveBeenCalled(); + expect(fetchJson).toHaveBeenCalled(); - const schema = schemaLoader.schema; - expect(schema).toBeDefined(); + const schema = schemaLoader.schema; + expect(schema).toBeDefined(); - const enums = schema.enums; - expect(enums.treeType).toBeDefined(); + const enums = schema.enums; + expect(enums.treeType).toBeDefined(); - const classes = schema.classes; - expect(classes.tree).toBeDefined(); - }); + const classes = schema.classes; + expect(classes.tree).toBeDefined(); }); - it("destroys schema", function () { + it("destroys schema", async function () { spyOn(Resource.prototype, "fetchJson").and.returnValue( Promise.resolve(schemaJson) ); @@ -138,27 +128,20 @@ describe("Scene/MetadataSchemaLoader", function () { expect(schemaLoader.schema).not.toBeDefined(); - schemaLoader.load(); + await schemaLoader.load(); - return schemaLoader.promise.then(function (schemaLoader) { - expect(schemaLoader.schema).toBeDefined(); - expect(schemaLoader.isDestroyed()).toBe(false); + expect(schemaLoader.schema).toBeDefined(); + expect(schemaLoader.isDestroyed()).toBe(false); - schemaLoader.destroy(); - expect(schemaLoader.schema).not.toBeDefined(); - expect(schemaLoader.isDestroyed()).toBe(true); - }); + schemaLoader.destroy(); + expect(schemaLoader.schema).not.toBeDefined(); + expect(schemaLoader.isDestroyed()).toBe(true); }); - function resolveJsonAfterDestroy(rejectPromise) { - const promise = new Promise(function (resolve, reject) { - if (rejectPromise) { - reject(new Error()); - } else { - resolve(schemaJson); - } - }); - spyOn(Resource.prototype, "fetchJson").and.returnValue(promise); + async function resolveJsonAfterDestroy(rejectPromise) { + spyOn(Resource.prototype, "fetchJson").and.callFake(() => + rejectPromise ? Promise.reject(new Error()) : Promise.resolve(schemaJson) + ); const schemaLoader = new MetadataSchemaLoader({ resource: resource, @@ -166,13 +149,13 @@ describe("Scene/MetadataSchemaLoader", function () { expect(schemaLoader.schema).not.toBeDefined(); - schemaLoader.load(); + const promise = schemaLoader.load(); expect(schemaLoader._state).toBe(ResourceLoaderState.LOADING); schemaLoader.destroy(); - return schemaLoader.promise.then(function () { - expect(schemaLoader.schema).not.toBeDefined(); - expect(schemaLoader.isDestroyed()).toBe(true); - }); + + await expectAsync(promise).toBeResolved(); + expect(schemaLoader.schema).not.toBeDefined(); + expect(schemaLoader.isDestroyed()).toBe(true); } it("handles resolving json after destroy", function () { diff --git a/packages/engine/Specs/Scene/Model/ModelAnimationCollectionSpec.js b/packages/engine/Specs/Scene/Model/ModelAnimationCollectionSpec.js index e8a16c329404..17e690351336 100644 --- a/packages/engine/Specs/Scene/Model/ModelAnimationCollectionSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelAnimationCollectionSpec.js @@ -7,7 +7,7 @@ import { } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; -import loadAndZoomToModel from "./loadAndZoomToModel.js"; +import loadAndZoomToModelAsync from "./loadAndZoomToModelAsync.js"; import pollToPromise from "../../../../../Specs/pollToPromise.js"; describe( @@ -38,7 +38,7 @@ describe( }); it("initializes", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -51,8 +51,8 @@ describe( }); }); - it("throws when add is called on non-ready model", function () { - const model = Model.fromGltf({ + it("throws when add is called on non-ready model", async function () { + const model = await Model.fromGltfAsync({ gltf: animatedTriangleUrl, }); @@ -64,7 +64,7 @@ describe( }); it("throws when add is not given a name or index", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -77,7 +77,7 @@ describe( }); it("throws when add is given invalid name", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -92,7 +92,7 @@ describe( }); it("throws when add is given invalid index", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -107,7 +107,7 @@ describe( }); it("throws when add is given invalid multiplier", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -123,7 +123,7 @@ describe( }); it("add works with name", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -150,7 +150,7 @@ describe( }); it("add works with index", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -177,7 +177,7 @@ describe( }); it("add works with options", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -215,8 +215,8 @@ describe( }); }); - it("throws when addAll is called on non-ready model", function () { - const model = Model.fromGltf({ + it("throws when addAll is called on non-ready model", async function () { + const model = await Model.fromGltfAsync({ gltf: animatedTriangleUrl, }); @@ -226,7 +226,7 @@ describe( }); it("throws when addAll is given invalid multiplier", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: interpolationTestUrl, }, @@ -241,7 +241,7 @@ describe( }); it("addAll works", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: interpolationTestUrl, }, @@ -270,7 +270,7 @@ describe( }); it("addAll works with options", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: interpolationTestUrl, }, @@ -312,7 +312,7 @@ describe( }); it("contains returns false for undefined", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -325,13 +325,13 @@ describe( }); it("contains returns false for animation not in collection", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, scene ).then(function (firstModel) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -346,7 +346,7 @@ describe( }); it("contains returns true for animation in collection", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -359,7 +359,7 @@ describe( }); it("throws when get is not given index", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -374,7 +374,7 @@ describe( }); it("throws when get is given out-of-range index", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -389,7 +389,7 @@ describe( }); it("get works", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: interpolationTestUrl, }, @@ -402,7 +402,7 @@ describe( }); it("remove returns false for undefined", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -415,13 +415,13 @@ describe( }); it("remove returns false for animation not in collection", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, scene ).then(function (firstModel) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -436,7 +436,7 @@ describe( }); it("remove returns true for animation in collection", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: interpolationTestUrl, }, @@ -456,7 +456,7 @@ describe( }); it("removeAll works", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: interpolationTestUrl, }, @@ -471,7 +471,7 @@ describe( }); it("update returns false when there are no animations", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: interpolationTestUrl, }, @@ -484,7 +484,7 @@ describe( }); it("raises animation start, update, and stop events when removeOnStop is true", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -536,7 +536,7 @@ describe( }); it("finishes animation when it reaches its end", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -568,7 +568,7 @@ describe( }); it("animates with a delay", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -595,7 +595,7 @@ describe( }); it("animates with startTime", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -625,7 +625,7 @@ describe( }); it("animates with an explicit stopTime", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -664,7 +664,7 @@ describe( }); it("animates with an explicit animation time", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -719,7 +719,7 @@ describe( }); it("animates while paused with an explicit animation time", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -764,7 +764,7 @@ describe( }); it("animates with a multiplier", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -797,7 +797,7 @@ describe( }); it("animates with reverse", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -830,7 +830,7 @@ describe( }); it("animates with REPEAT", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, @@ -867,7 +867,7 @@ describe( }); it("animates with MIRRORED_REPEAT", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, }, diff --git a/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js b/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js index 6ad1c434230d..d4c912fff10b 100644 --- a/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js @@ -10,7 +10,7 @@ import { Quaternion, } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; -import loadAndZoomToModel from "./loadAndZoomToModel.js"; +import loadAndZoomToModelAsync from "./loadAndZoomToModelAsync.js"; describe( "Scene/Model/ModelMatrixUpdateStage", @@ -118,7 +118,7 @@ describe( } it("updates leaf nodes using node transform setter", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: simpleSkin, }, @@ -196,7 +196,7 @@ describe( } it("updates nodes with children using node transform setter", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: simpleSkin, }, @@ -258,7 +258,7 @@ describe( }); it("updates with new model matrix", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: simpleSkin, }, @@ -305,7 +305,7 @@ describe( }); it("updates with new model matrix and model scale", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: simpleSkin, }, @@ -360,7 +360,7 @@ describe( }); it("updates render state cull face when scale is negative", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: simpleSkin, }, diff --git a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js index a66c117689d3..e3e8f2873cd9 100644 --- a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js @@ -13,7 +13,7 @@ import { ResourceCache, } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; -import loadAndZoomToModel from "./loadAndZoomToModel.js"; +import loadAndZoomToModelAsync from "./loadAndZoomToModelAsync.js"; describe( "Scene/Model/ModelSceneGraph", @@ -45,7 +45,7 @@ describe( }); it("creates runtime nodes and runtime primitives from a model", function () { - return loadAndZoomToModel({ gltf: vertexColorGltfUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: vertexColorGltfUrl }, scene).then( function (model) { const sceneGraph = model._sceneGraph; const components = sceneGraph._components; @@ -68,7 +68,7 @@ describe( }, }); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, @@ -95,7 +95,7 @@ describe( conditions: [["${height} > 1", "color('red', 0.1)"]], }, }); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, @@ -126,7 +126,7 @@ describe( }, }); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, @@ -151,54 +151,58 @@ describe( it("builds draw commands for each primitive", function () { spyOn(ModelSceneGraph.prototype, "buildDrawCommands").and.callThrough(); spyOn(ModelSceneGraph.prototype, "pushDrawCommands").and.callThrough(); - return loadAndZoomToModel({ gltf: parentGltfUrl }, scene).then(function ( - model - ) { - const sceneGraph = model._sceneGraph; - const runtimeNodes = sceneGraph._runtimeNodes; + return loadAndZoomToModelAsync({ gltf: parentGltfUrl }, scene).then( + function (model) { + const sceneGraph = model._sceneGraph; + const runtimeNodes = sceneGraph._runtimeNodes; - let primitivesCount = 0; - for (let i = 0; i < runtimeNodes.length; i++) { - primitivesCount += runtimeNodes[i].runtimePrimitives.length; + let primitivesCount = 0; + for (let i = 0; i < runtimeNodes.length; i++) { + primitivesCount += runtimeNodes[i].runtimePrimitives.length; + } + + const frameState = scene.frameState; + frameState.commandList.length = 0; + scene.renderForSpecs(); + expect( + ModelSceneGraph.prototype.buildDrawCommands + ).toHaveBeenCalled(); + expect(ModelSceneGraph.prototype.pushDrawCommands).toHaveBeenCalled(); + expect(frameState.commandList.length).toEqual(primitivesCount); + + expect(model._drawCommandsBuilt).toEqual(true); + + // Reset the draw command list to see if they're re-built. + model._drawCommandsBuilt = false; + frameState.commandList.length = 0; + scene.renderForSpecs(); + expect( + ModelSceneGraph.prototype.buildDrawCommands + ).toHaveBeenCalled(); + expect(ModelSceneGraph.prototype.pushDrawCommands).toHaveBeenCalled(); + expect(frameState.commandList.length).toEqual(primitivesCount); } - - const frameState = scene.frameState; - frameState.commandList.length = 0; - scene.renderForSpecs(); - expect(ModelSceneGraph.prototype.buildDrawCommands).toHaveBeenCalled(); - expect(ModelSceneGraph.prototype.pushDrawCommands).toHaveBeenCalled(); - expect(frameState.commandList.length).toEqual(primitivesCount); - - expect(model._drawCommandsBuilt).toEqual(true); - - // Reset the draw command list to see if they're re-built. - model._drawCommandsBuilt = false; - frameState.commandList.length = 0; - scene.renderForSpecs(); - expect(ModelSceneGraph.prototype.buildDrawCommands).toHaveBeenCalled(); - expect(ModelSceneGraph.prototype.pushDrawCommands).toHaveBeenCalled(); - expect(frameState.commandList.length).toEqual(primitivesCount); - }); + ); }); it("stores runtime nodes correctly", function () { - return loadAndZoomToModel({ gltf: parentGltfUrl }, scene).then(function ( - model - ) { - const sceneGraph = model._sceneGraph; - const components = sceneGraph._components; - const runtimeNodes = sceneGraph._runtimeNodes; + return loadAndZoomToModelAsync({ gltf: parentGltfUrl }, scene).then( + function (model) { + const sceneGraph = model._sceneGraph; + const components = sceneGraph._components; + const runtimeNodes = sceneGraph._runtimeNodes; - expect(runtimeNodes[0].node).toEqual(components.nodes[0]); - expect(runtimeNodes[1].node).toEqual(components.nodes[1]); + expect(runtimeNodes[0].node).toEqual(components.nodes[0]); + expect(runtimeNodes[1].node).toEqual(components.nodes[1]); - const rootNodes = sceneGraph._rootNodes; - expect(rootNodes[0]).toEqual(0); - }); + const rootNodes = sceneGraph._rootNodes; + expect(rootNodes[0]).toEqual(0); + } + ); }); it("propagates node transforms correctly", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: parentGltfUrl, upAxis: Axis.Z, @@ -227,7 +231,7 @@ describe( }); it("creates runtime skin from model", function () { - return loadAndZoomToModel({ gltf: simpleSkinGltfUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: simpleSkinGltfUrl }, scene).then( function (model) { const sceneGraph = model._sceneGraph; const components = sceneGraph._components; @@ -258,7 +262,7 @@ describe( }); it("creates articulation from model", function () { - return loadAndZoomToModel({ gltf: boxArticulationsUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: boxArticulationsUrl }, scene).then( function (model) { const sceneGraph = model._sceneGraph; const components = sceneGraph._components; @@ -281,7 +285,7 @@ describe( }); it("applies articulations", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxArticulationsUrl, }, @@ -326,7 +330,7 @@ describe( it("adds ModelColorPipelineStage when color is set on the model", function () { spyOn(ModelColorPipelineStage, "process"); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { color: Color.RED, gltf: parentGltfUrl, @@ -339,7 +343,7 @@ describe( it("adds CustomShaderPipelineStage when customShader is set on the model", function () { spyOn(CustomShaderPipelineStage, "process"); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, @@ -352,7 +356,7 @@ describe( }); it("pushDrawCommands ignores hidden nodes", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: duckUrl, }, diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index 180ab45ec0ec..2ccd2cb068de 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -34,6 +34,7 @@ import { PrimitiveType, Resource, ResourceCache, + RuntimeError, ShaderProgram, ShadowMode, SplitDirection, @@ -43,7 +44,7 @@ import { } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; import pollToPromise from "../../../../../Specs/pollToPromise.js"; -import loadAndZoomToModel from "./loadAndZoomToModel.js"; +import loadAndZoomToModelAsync from "./loadAndZoomToModelAsync.js"; describe( "Scene/Model/Model", @@ -254,25 +255,36 @@ describe( }).toThrowDeveloperError(); }); + it("fromGltfAsync throws with undefined options", async function () { + await expectAsync(Model.fromGltfAsync()).toBeRejectedWithDeveloperError(); + }); + + it("fromGltfAsync throws with undefined url", async function () { + await expectAsync( + Model.fromGltfAsync({}) + ).toBeRejectedWithDeveloperError(); + }); + it("initializes and renders from Uint8Array", function () { const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel({ gltf: new Uint8Array(buffer) }, scene).then( - function (model) { - expect(model.ready).toEqual(true); - expect(model._sceneGraph).toBeDefined(); - expect(model._resourcesLoaded).toEqual(true); - verifyRender(model, true); - } - ); + return loadAndZoomToModelAsync( + { gltf: new Uint8Array(buffer) }, + scene + ).then(function (model) { + expect(model.ready).toEqual(true); + expect(model._sceneGraph).toBeDefined(); + expect(model._resourcesLoaded).toEqual(true); + verifyRender(model, true); + }); }); }); it("initializes and renders from JSON object", function () { const resource = Resource.createIfNeeded(boxTexturedGltfUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: boxTexturedGltfUrl, @@ -290,7 +302,7 @@ describe( it("initializes and renders from JSON object with external buffers", function () { const resource = Resource.createIfNeeded(microcosm); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: microcosm, @@ -306,7 +318,7 @@ describe( }); it("initializes and renders with url", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { url: boxTexturedGltfUrl, }, @@ -319,6 +331,60 @@ describe( }); }); + it("raises errorEvent when a texture fails to load and incrementallyLoadTextures is true", async function () { + const resource = Resource.createIfNeeded(boxTexturedGltfUrl); + const gltf = await resource.fetchJson(); + gltf.images[0].uri = "non-existent-path.png"; + const model = await Model.fromGltfAsync({ + gltf: gltf, + basePath: boxTexturedGltfUrl, + incrementallyLoadTextures: true, + }); + scene.primitives.add(model); + let finished = false; + + model.errorEvent.addEventListener((e) => { + expect(e).toBeInstanceOf(RuntimeError); + expect(e.message).toContain( + `Failed to load model: ${boxTexturedGltfUrl}` + ); + expect(e.message).toContain("Failed to load texture"); + finished = true; + }); + + return pollToPromise(function () { + scene.renderForSpecs(); + return finished; + }); + }); + + it("raises errorEvent when a texture fails to load and incrementallyLoadTextures is false", async function () { + const resource = Resource.createIfNeeded(boxTexturedGltfUrl); + const gltf = await resource.fetchJson(); + gltf.images[0].uri = "non-existent-path.png"; + const model = await Model.fromGltfAsync({ + gltf: gltf, + basePath: boxTexturedGltfUrl, + incrementallyLoadTextures: false, + }); + scene.primitives.add(model); + let finished = false; + + model.errorEvent.addEventListener((e) => { + expect(e).toBeInstanceOf(RuntimeError); + expect(e.message).toContain( + `Failed to load model: ${boxTexturedGltfUrl}` + ); + expect(e.message).toContain("Failed to load texture"); + finished = true; + }); + + return pollToPromise(function () { + scene.renderForSpecs(); + return finished; + }); + }); + it("rejects ready promise when texture fails to load", function () { const resource = Resource.createIfNeeded(boxTexturedGltfUrl); return resource.fetchJson().then(function (gltf) { @@ -358,23 +424,60 @@ describe( }); }); + it("raises errorEvent when external buffer fails to load", async function () { + const resource = Resource.createIfNeeded(boxTexturedGltfUrl); + const gltf = await resource.fetchJson(); + gltf.buffers[0].uri = "non-existent-path.bin"; + const model = await Model.fromGltfAsync({ + gltf: gltf, + basePath: boxTexturedGltfUrl, + incrementallyLoadTextures: false, + }); + scene.primitives.add(model); + let finished = false; + + model.errorEvent.addEventListener((e) => { + expect(e).toBeInstanceOf(RuntimeError); + expect(e.message).toContain( + `Failed to load model: ${boxTexturedGltfUrl}` + ); + expect(e.message).toContain("Failed to load vertex buffer"); + finished = true; + }); + + return pollToPromise(function () { + scene.renderForSpecs(); + return finished; + }); + }); + it("rejects ready promise when external buffer fails to load", function () { const resource = Resource.createIfNeeded(boxTexturedGltfUrl); return resource.fetchJson().then(function (gltf) { gltf.buffers[0].uri = "non-existent-path.bin"; - return loadAndZoomToModel( - { - gltf: gltf, - basePath: boxTexturedGltfUrl, - }, - scene - ) + const model = Model.fromGltf({ + gltf: gltf, + basePath: boxTexturedGltfUrl, + }); + scene.primitives.add(model); + let finished = false; + model.readyPromise .then(function (model) { + finished = true; fail(); }) .catch(function (error) { + finished = true; expect(error).toBeDefined(); }); + + return pollToPromise( + function () { + scene.renderForSpecs(); + return finished; + }, + { timeout: 10000 } + ); }); }); @@ -384,7 +487,7 @@ describe( "execute" ).and.callThrough(); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, asynchronous: true, @@ -404,7 +507,7 @@ describe( "execute" ).and.callThrough(); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, asynchronous: false, @@ -419,7 +522,7 @@ describe( }); it("initializes feature table", function () { - return loadAndZoomToModel({ gltf: buildingsMetadata }, scene).then( + return loadAndZoomToModelAsync({ gltf: buildingsMetadata }, scene).then( function (model) { expect(model.ready).toEqual(true); expect(model.featureTables).toBeDefined(); @@ -446,7 +549,7 @@ describe( }); it("sets default properties", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, }, @@ -502,7 +605,7 @@ describe( it("renders model without indices", function () { const resource = Resource.createIfNeeded(triangleWithoutIndicesUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: triangleWithoutIndicesUrl, @@ -533,7 +636,7 @@ describe( it("renders model with vertex colors", function () { const resource = Resource.createIfNeeded(vertexColorTestUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: vertexColorTestUrl, @@ -569,7 +672,7 @@ describe( it("renders model with double-sided material", function () { const resource = Resource.createIfNeeded(twoSidedPlaneUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: twoSidedPlaneUrl, @@ -623,7 +726,7 @@ describe( xit("renders model with emissive texture", function () { const resource = Resource.createIfNeeded(emissiveTextureUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: emissiveTextureUrl, @@ -653,7 +756,7 @@ describe( const resource = Resource.createIfNeeded(boomBoxUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: boomBoxUrl, @@ -675,7 +778,7 @@ describe( const resource = Resource.createIfNeeded(morphPrimitivesTestUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: morphPrimitivesTestUrl, @@ -701,57 +804,52 @@ describe( }); it("renders Draco-compressed model", function () { - return loadAndZoomToModel({ gltf: dracoCesiumManUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: dracoCesiumManUrl }, scene).then( function (model) { verifyRender(model, true); } ); }); - it("fails to load with Draco decoding error", function () { - const readyPromise = pollToPromise(function () { + it("fails to load with Draco decoding error", async function () { + DracoLoader._getDecoderTaskProcessor(); + await pollToPromise(function () { return DracoLoader._taskProcessorReady; }); - DracoLoader._getDecoderTaskProcessor(); - return readyPromise - .then(function () { - const decoder = DracoLoader._getDecoderTaskProcessor(); - spyOn(decoder, "scheduleTask").and.callFake(function () { - return Promise.reject({ message: "Custom error" }); - }); - const model = scene.primitives.add( - Model.fromGltf({ - url: dracoCesiumManUrl, - }) - ); + const decoder = DracoLoader._getDecoderTaskProcessor(); + spyOn(decoder, "scheduleTask").and.callFake(function () { + return Promise.reject({ message: "Custom error" }); + }); - return Promise.all([ - pollToPromise( - function () { - scene.renderForSpecs(); - return model.loader._state === 7; // FAILED - }, - { timeout: 10000 } - ), - model.readyPromise, - ]); + const model = scene.primitives.add( + await Model.fromGltfAsync({ + url: dracoCesiumManUrl, }) - .then(function (e) { - fail("Should not resolve"); - }) - .catch(function (e) { - expect(e).toBeDefined(); - expect( - e.message.includes(`Failed to load model: ${dracoCesiumManUrl}`) - ).toBe(true); - expect(e.message.includes(`Failed to load Draco`)).toBe(true); - expect(e.message.includes(`Custom error`)).toBe(true); - }); + ); + + let failed = false; + model.errorEvent.addEventListener((e) => { + expect(e).toBeInstanceOf(RuntimeError); + expect(e.message).toContain( + `Failed to load model: ${dracoCesiumManUrl}` + ); + expect(e.message).toContain("Failed to load Draco"); + expect(e.message).toContain("Custom error"); + failed = true; + }); + + await pollToPromise( + function () { + scene.renderForSpecs(); + return failed; + }, + { timeout: 10000 } + ); }); it("renders model without animations added", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, offset: animatedTriangleOffset, @@ -771,7 +869,7 @@ describe( }); it("renders model with animations added", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: animatedTriangleUrl, offset: animatedTriangleOffset, @@ -804,7 +902,7 @@ describe( }); it("renders model with CESIUM_RTC extension", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxCesiumRtcUrl, }, @@ -815,7 +913,7 @@ describe( }); it("adds animation to draco-compressed model", function () { - return loadAndZoomToModel({ gltf: dracoCesiumManUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: dracoCesiumManUrl }, scene).then( function (model) { verifyRender(model, true); @@ -840,7 +938,7 @@ describe( const resource = Resource.createIfNeeded(boxInstancedNoNormalsUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: boxInstancedNoNormalsUrl, @@ -861,7 +959,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer), show: false }, scene ).then(function (model) { @@ -877,7 +975,7 @@ describe( }); it("renders in 2D", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: modelMatrix, @@ -893,7 +991,7 @@ describe( }); it("renders in 2D over the IDL", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -919,7 +1017,7 @@ describe( }); it("renders in CV", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: modelMatrix, @@ -936,7 +1034,7 @@ describe( }); it("renders in CV after draw commands are reset", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: modelMatrix, @@ -959,7 +1057,7 @@ describe( }); it("projectTo2D works for 2D", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: modelMatrix, @@ -977,7 +1075,7 @@ describe( }); it("projectTo2D works for CV", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: modelMatrix, @@ -996,7 +1094,7 @@ describe( }); it("does not render during morph", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: modelMatrix, @@ -1024,7 +1122,7 @@ describe( it("renders model with style", function () { let model; let style; - return loadAndZoomToModel({ gltf: buildingsMetadata }, scene).then( + return loadAndZoomToModelAsync({ gltf: buildingsMetadata }, scene).then( function (result) { model = result; // Renders without style. @@ -1088,7 +1186,7 @@ describe( const credit = new Credit("User Credit"); const resource = Resource.createIfNeeded(boxTexturedGltfUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: boxTexturedGltfUrl, @@ -1111,7 +1209,7 @@ describe( const creditString = "User Credit"; const resource = Resource.createIfNeeded(boxTexturedGltfUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: boxTexturedGltfUrl, @@ -1133,7 +1231,7 @@ describe( it("gets copyrights from gltf", function () { const resource = Resource.createIfNeeded(boxWithCreditsUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: boxWithCreditsUrl, @@ -1162,7 +1260,7 @@ describe( it("displays all types of credits", function () { const resource = Resource.createIfNeeded(boxWithCreditsUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: boxWithCreditsUrl, @@ -1195,7 +1293,7 @@ describe( it("initializes with showCreditsOnScreen", function () { const resource = Resource.createIfNeeded(boxWithCreditsUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: boxWithCreditsUrl, @@ -1227,7 +1325,7 @@ describe( it("changing showCreditsOnScreen works", function () { const resource = Resource.createIfNeeded(boxWithCreditsUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: boxWithCreditsUrl, @@ -1285,7 +1383,7 @@ describe( it("showCreditsOnScreen overrides existing credit setting", function () { const resource = Resource.createIfNeeded(boxTexturedGltfUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: gltf, basePath: boxTexturedGltfUrl, @@ -1336,7 +1434,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer), enableDebugWireframe: true }, sceneWithWebgl1 ).then(function (model) { @@ -1349,7 +1447,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer), enableDebugWireframe: false }, sceneWithWebgl1 ).then(function (model) { @@ -1383,7 +1481,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer) }, scene ).then(function (model) { @@ -1395,7 +1493,7 @@ describe( }); it("debugWireframe works for model without indices", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: triangleWithoutIndicesUrl, enableDebugWireframe: true }, scene ).then(function (model) { @@ -1406,7 +1504,7 @@ describe( }); it("debugWireframe works for model with triangle strip", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: triangleStripUrl, enableDebugWireframe: true }, scene ).then(function (model) { @@ -1415,7 +1513,7 @@ describe( }); it("debugWireframe works for model with triangle fan", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: triangleFanUrl, enableDebugWireframe: true }, scene ).then(function (model) { @@ -1424,7 +1522,7 @@ describe( }); it("debugWireframe ignores points", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: pointCloudUrl, enableDebugWireframe: true }, scene ).then(function (model) { @@ -1451,7 +1549,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer), debugShowBoundingVolume: true }, scene ).then(function (model) { @@ -1472,8 +1570,8 @@ describe( }); describe("boundingSphere", function () { - it("boundingSphere throws if model is not ready", function () { - const model = Model.fromGltf({ + it("boundingSphere throws if model is not ready", async function () { + const model = await Model.fromGltfAsync({ url: boxTexturedGlbUrl, }); expect(function () { @@ -1485,7 +1583,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer) }, scene ).then(function (model) { @@ -1503,7 +1601,7 @@ describe( it("boundingSphere accounts for axis correction", function () { const resource = Resource.createIfNeeded(riggedFigureUrl); return resource.fetchJson().then(function (gltf) { - return loadAndZoomToModel({ gltf: gltf }, scene).then(function ( + return loadAndZoomToModelAsync({ gltf: gltf }, scene).then(function ( model ) { // The bounding sphere should transform from z-forward @@ -1523,7 +1621,7 @@ describe( }); it("boundingSphere accounts for transform from CESIUM_RTC extension", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxCesiumRtcUrl, }, @@ -1536,7 +1634,7 @@ describe( }); it("boundingSphere updates bounding sphere when invoked", function () { - return loadAndZoomToModel({ gltf: boxTexturedGlbUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene).then( function (model) { const expectedRadius = 0.8660254037844386; const translation = new Cartesian3(10, 0, 0); @@ -1563,7 +1661,7 @@ describe( // the camera just a little const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, offset: offset, @@ -1584,7 +1682,7 @@ describe( // the camera just a little const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, offset: offset, @@ -1617,7 +1715,7 @@ describe( // the camera just a little const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, offset: offset, @@ -1641,7 +1739,7 @@ describe( // the camera just a little const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, offset: offset, @@ -1667,7 +1765,7 @@ describe( // the camera just a little const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, offset: offset, @@ -1700,7 +1798,7 @@ describe( // the camera just a little const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, allowPicking: false, @@ -1724,7 +1822,7 @@ describe( // the camera just a little const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, offset: offset, @@ -1761,7 +1859,7 @@ describe( } it("resets draw commands when the style commands needed are changed", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, @@ -1807,7 +1905,7 @@ describe( if (webglStub) { return; } - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxInstanced, instanceFeatureIdLabel: "section", @@ -1819,7 +1917,7 @@ describe( }); it("selects feature table for feature ID textures", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: microcosm, }, @@ -1830,7 +1928,7 @@ describe( }); it("selects feature table for feature ID attributes", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, @@ -1841,7 +1939,7 @@ describe( }); it("featureIdLabel setter works", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, @@ -1859,7 +1957,7 @@ describe( if (webglStub) { return; } - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxInstanced, }, @@ -1879,7 +1977,7 @@ describe( const translation = new Cartesian3(10, 0, 0); const transform = Matrix4.fromTranslation(translation); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, upAxis: Axis.Z, @@ -1905,7 +2003,7 @@ describe( ModelSceneGraph.prototype, "updateModelMatrix" ).and.callThrough(); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, upAxis: Axis.Z, forwardAxis: Axis.X }, scene ).then(function (model) { @@ -1932,7 +2030,7 @@ describe( it("changing model matrix affects bounding sphere", function () { const translation = new Cartesian3(10, 0, 0); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, upAxis: Axis.Z, forwardAxis: Axis.X }, scene ).then(function (model) { @@ -1951,7 +2049,7 @@ describe( }); it("changing model matrix in 2D mode works if projectTo2D is false", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: modelMatrix, @@ -1974,7 +2072,7 @@ describe( }); it("changing model matrix in 2D mode throws if projectTo2D is true", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: modelMatrix, @@ -2059,7 +2157,7 @@ describe( }); it("initializes with height reference", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, heightReference: HeightReference.CLAMP_TO_GROUND, @@ -2076,7 +2174,7 @@ describe( }); it("changing height reference works", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, heightReference: HeightReference.NONE, @@ -2100,7 +2198,7 @@ describe( }); it("creates height update callback when initializing with height reference", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -2119,7 +2217,7 @@ describe( }); it("creates height update callback after setting height reference", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -2143,7 +2241,7 @@ describe( }); it("updates height reference callback when the height reference changes", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -2173,7 +2271,7 @@ describe( const modelMatrix = Transforms.eastNorthUpToFixedFrame( Cartesian3.fromDegrees(-72.0, 40.0) ); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, modelMatrix: Matrix4.clone(modelMatrix), @@ -2204,7 +2302,7 @@ describe( }); it("height reference callback updates the position", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -2232,7 +2330,7 @@ describe( }); it("height reference accounts for change in terrain provider", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -2256,7 +2354,7 @@ describe( }); it("throws when initializing height reference with no scene", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -2274,7 +2372,7 @@ describe( }); it("throws when changing height reference with no scene", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -2292,7 +2390,7 @@ describe( }); it("throws when initializing height reference with no globe", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -2310,7 +2408,7 @@ describe( }); it("throws when changing height reference with no globe", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -2328,7 +2426,7 @@ describe( }); it("destroys height reference callback", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: Transforms.eastNorthUpToFixedFrame( @@ -2353,7 +2451,7 @@ describe( const near = 10.0; const far = 100.0; const condition = new DistanceDisplayCondition(near, far); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, distanceDisplayCondition: condition, @@ -2368,7 +2466,7 @@ describe( const near = 10.0; const far = 100.0; const condition = new DistanceDisplayCondition(near, far); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, }, @@ -2388,7 +2486,7 @@ describe( const near = 10.0; const far = 100.0; const condition = new DistanceDisplayCondition(near, far); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, }, @@ -2426,7 +2524,7 @@ describe( const near = 101.0; const far = 100.0; const condition = new DistanceDisplayCondition(near, far); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, }, @@ -2441,7 +2539,7 @@ describe( describe("model color", function () { it("initializes with model color", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, color: Color.BLACK }, scene ).then(function (model) { @@ -2450,20 +2548,21 @@ describe( }); it("changing model color works", function () { - return loadAndZoomToModel({ gltf: boxTexturedGltfUrl }, scene).then( - function (model) { - verifyRender(model, true); + return loadAndZoomToModelAsync( + { gltf: boxTexturedGltfUrl }, + scene + ).then(function (model) { + verifyRender(model, true); - model.color = Color.BLACK; - verifyRender(model, false); + model.color = Color.BLACK; + verifyRender(model, false); - model.color = Color.RED; - verifyRender(model, true); + model.color = Color.RED; + verifyRender(model, true); - model.color = undefined; - verifyRender(model, true); - } - ); + model.color = undefined; + verifyRender(model, true); + }); }); it("renders with translucent color", function () { @@ -2471,7 +2570,7 @@ describe( // the camera just a little const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, offset: offset, @@ -2503,7 +2602,7 @@ describe( // the camera just a little const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, color: Color.fromAlpha(Color.BLACK, 0.0), @@ -2553,7 +2652,7 @@ describe( const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); it("initializes with ColorBlendMode.HIGHLIGHT", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, offset: offset, @@ -2575,7 +2674,7 @@ describe( }); it("initializes with ColorBlendMode.REPLACE", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, offset: offset, @@ -2597,7 +2696,7 @@ describe( }); it("initializes with ColorBlendMode.MIX", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, offset: offset, @@ -2619,7 +2718,7 @@ describe( }); it("toggles colorBlendMode", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, offset: offset, @@ -2661,7 +2760,7 @@ describe( const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); it("initializes with colorBlendAmount", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, offset: offset, @@ -2686,7 +2785,7 @@ describe( }); it("changing colorBlendAmount works", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, offset: offset, @@ -2736,7 +2835,7 @@ describe( describe("silhouette", function () { it("initializes with silhouette size", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, silhouetteSize: 1.0 }, scene ).then(function (model) { @@ -2751,33 +2850,34 @@ describe( }); it("changing silhouette size works", function () { - return loadAndZoomToModel({ gltf: boxTexturedGltfUrl }, scene).then( - function (model) { - const commands = scene.frameState.commandList; - scene.renderForSpecs(); - expect(commands.length).toBe(1); - expect(commands[0].renderState.stencilTest.enabled).toBe(false); - expect(commands[0].pass).toBe(Pass.OPAQUE); + return loadAndZoomToModelAsync( + { gltf: boxTexturedGltfUrl }, + scene + ).then(function (model) { + const commands = scene.frameState.commandList; + scene.renderForSpecs(); + expect(commands.length).toBe(1); + expect(commands[0].renderState.stencilTest.enabled).toBe(false); + expect(commands[0].pass).toBe(Pass.OPAQUE); - model.silhouetteSize = 1.0; - scene.renderForSpecs(); - expect(commands.length).toBe(2); - expect(commands[0].renderState.stencilTest.enabled).toBe(true); - expect(commands[0].pass).toBe(Pass.OPAQUE); - expect(commands[1].renderState.stencilTest.enabled).toBe(true); - expect(commands[1].pass).toBe(Pass.OPAQUE); + model.silhouetteSize = 1.0; + scene.renderForSpecs(); + expect(commands.length).toBe(2); + expect(commands[0].renderState.stencilTest.enabled).toBe(true); + expect(commands[0].pass).toBe(Pass.OPAQUE); + expect(commands[1].renderState.stencilTest.enabled).toBe(true); + expect(commands[1].pass).toBe(Pass.OPAQUE); - model.silhouetteSize = 0.0; - scene.renderForSpecs(); - expect(commands.length).toBe(1); - expect(commands[0].renderState.stencilTest.enabled).toBe(false); - expect(commands[0].pass).toBe(Pass.OPAQUE); - } - ); + model.silhouetteSize = 0.0; + scene.renderForSpecs(); + expect(commands.length).toBe(1); + expect(commands[0].renderState.stencilTest.enabled).toBe(false); + expect(commands[0].pass).toBe(Pass.OPAQUE); + }); }); it("silhouette works with translucent color", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, silhouetteSize: 1.0, @@ -2796,7 +2896,7 @@ describe( }); it("silhouette is disabled by invisible color", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, silhouetteSize: 1.0 }, scene ).then(function (model) { @@ -2817,7 +2917,7 @@ describe( }); it("silhouette works for invisible model", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, silhouetteSize: 1.0, @@ -2843,7 +2943,7 @@ describe( }); it("silhouette works for translucent model", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, silhouetteSize: 1.0, @@ -2865,7 +2965,7 @@ describe( }); it("silhouette works for translucent model and translucent silhouette color", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, silhouetteSize: 1.0, @@ -2886,14 +2986,14 @@ describe( }); it("silhouette works for multiple models", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, silhouetteSize: 1.0, }, scene ).then(function (model) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, silhouetteSize: 1.0, @@ -2920,7 +3020,7 @@ describe( describe("light color", function () { it("initializes with light color", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, lightColor: Cartesian3.ZERO }, scene ).then(function (model) { @@ -2929,34 +3029,35 @@ describe( }); it("changing light color works", function () { - return loadAndZoomToModel({ gltf: boxTexturedGltfUrl }, scene).then( - function (model) { - model.lightColor = Cartesian3.ZERO; - verifyRender(model, false); + return loadAndZoomToModelAsync( + { gltf: boxTexturedGltfUrl }, + scene + ).then(function (model) { + model.lightColor = Cartesian3.ZERO; + verifyRender(model, false); - model.lightColor = new Cartesian3(1.0, 0.0, 0.0); - verifyRender(model, true); + model.lightColor = new Cartesian3(1.0, 0.0, 0.0); + verifyRender(model, true); - model.lightColor = undefined; - verifyRender(model, true); - } - ); + model.lightColor = undefined; + verifyRender(model, true); + }); }); it("light color doesn't affect unlit models", function () { - return loadAndZoomToModel({ gltf: boxUnlitUrl }, scene).then(function ( - model - ) { - const options = { - zoomToModel: false, - }; - // Move the camera to face one of the two boxes. - scene.camera.moveRight(1.0); - verifyRender(model, true, options); + return loadAndZoomToModelAsync({ gltf: boxUnlitUrl }, scene).then( + function (model) { + const options = { + zoomToModel: false, + }; + // Move the camera to face one of the two boxes. + scene.camera.moveRight(1.0); + verifyRender(model, true, options); - model.lightColor = Cartesian3.ZERO; - verifyRender(model, true, options); - }); + model.lightColor = Cartesian3.ZERO; + verifyRender(model, true, options); + } + ); }); }); @@ -2970,7 +3071,7 @@ describe( imageBasedLightingFactor: Cartesian2.ZERO, luminanceAtZenith: 0.5, }); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, imageBasedLighting: ibl }, scene ).then(function (model) { @@ -2979,52 +3080,54 @@ describe( }); it("creates default imageBasedLighting", function () { - return loadAndZoomToModel({ gltf: boxTexturedGltfUrl }, scene).then( - function (model) { - const imageBasedLighting = model.imageBasedLighting; - expect(imageBasedLighting).toBeDefined(); - expect( - Cartesian2.equals( - imageBasedLighting.imageBasedLightingFactor, - new Cartesian2(1, 1) - ) - ).toBe(true); - expect(imageBasedLighting.luminanceAtZenith).toBe(0.2); - expect( - imageBasedLighting.sphericalHarmonicCoefficients - ).toBeUndefined(); - expect(imageBasedLighting.specularEnvironmentMaps).toBeUndefined(); - } - ); + return loadAndZoomToModelAsync( + { gltf: boxTexturedGltfUrl }, + scene + ).then(function (model) { + const imageBasedLighting = model.imageBasedLighting; + expect(imageBasedLighting).toBeDefined(); + expect( + Cartesian2.equals( + imageBasedLighting.imageBasedLightingFactor, + new Cartesian2(1, 1) + ) + ).toBe(true); + expect(imageBasedLighting.luminanceAtZenith).toBe(0.2); + expect( + imageBasedLighting.sphericalHarmonicCoefficients + ).toBeUndefined(); + expect(imageBasedLighting.specularEnvironmentMaps).toBeUndefined(); + }); }); it("changing imageBasedLighting works", function () { const imageBasedLighting = new ImageBasedLighting({ imageBasedLightingFactor: Cartesian2.ZERO, }); - return loadAndZoomToModel({ gltf: boxTexturedGltfUrl }, scene).then( - function (model) { - const renderOptions = { - scene: scene, - time: defaultDate, - }; + return loadAndZoomToModelAsync( + { gltf: boxTexturedGltfUrl }, + scene + ).then(function (model) { + const renderOptions = { + scene: scene, + time: defaultDate, + }; - let result; - verifyRender(model, true); - expect(renderOptions).toRenderAndCall(function (rgba) { - result = rgba; - }); + let result; + verifyRender(model, true); + expect(renderOptions).toRenderAndCall(function (rgba) { + result = rgba; + }); - model.imageBasedLighting = imageBasedLighting; - expect(renderOptions).toRenderAndCall(function (rgba) { - expect(rgba).not.toEqual(result); - }); - } - ); + model.imageBasedLighting = imageBasedLighting; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual(result); + }); + }); }); it("changing imageBasedLightingFactor works", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, imageBasedLighting: new ImageBasedLighting({ @@ -3053,7 +3156,7 @@ describe( }); it("changing luminanceAtZenith works", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, imageBasedLighting: new ImageBasedLighting({ @@ -3131,7 +3234,7 @@ describe( 0.121102528320197 ); // L22, irradiance, pre-scaled base const coefficients = [L00, L1_1, L10, L11, L2_2, L2_1, L20, L21, L22]; - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, imageBasedLighting: new ImageBasedLighting({ @@ -3166,7 +3269,7 @@ describe( return; } const url = "./Data/EnvironmentMap/kiara_6_afternoon_2k_ibl.ktx2"; - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boomBoxUrl, scale: 10.0, @@ -3209,7 +3312,7 @@ describe( it("renders when specularEnvironmentMaps aren't supported", function () { spyOn(OctahedralProjectedCubeMap, "isSupported").and.returnValue(false); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boomBoxUrl, scale: 10.0, @@ -3224,7 +3327,7 @@ describe( describe("scale", function () { it("initializes with scale", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, upAxis: Axis.Z, @@ -3246,7 +3349,7 @@ describe( ModelSceneGraph.prototype, "updateModelMatrix" ).and.callThrough(); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, upAxis: Axis.Z, @@ -3271,7 +3374,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer), scale: 10, @@ -3308,7 +3411,7 @@ describe( const resource = Resource.createIfNeeded(boxWithOffsetUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer), scale: 10, @@ -3365,7 +3468,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer), upAxis: Axis.Z, @@ -3398,7 +3501,7 @@ describe( ModelSceneGraph.prototype, "updateModelMatrix" ).and.callThrough(); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, upAxis: Axis.Z, @@ -3433,7 +3536,7 @@ describe( ModelSceneGraph.prototype, "updateModelMatrix" ).and.callThrough(); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, upAxis: Axis.Z, @@ -3478,7 +3581,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer), upAxis: Axis.Z, @@ -3500,7 +3603,7 @@ describe( ModelSceneGraph.prototype, "updateModelMatrix" ).and.callThrough(); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, upAxis: Axis.Z, @@ -3528,7 +3631,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer), scale: 20, @@ -3566,7 +3669,7 @@ describe( const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const loadPromise = resource.fetchArrayBuffer(); return loadPromise.then(function (buffer) { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: new Uint8Array(buffer), minimumPixelSize: 1, @@ -3602,7 +3705,7 @@ describe( }); it("does not issue draw commands when ignoreCommands is true", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, }, @@ -3618,7 +3721,7 @@ describe( describe("frustum culling ", function () { it("enables frustum culling", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, cull: true, @@ -3641,7 +3744,7 @@ describe( }); it("disables frustum culling", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, cull: false, @@ -3675,7 +3778,7 @@ describe( ); it("enables back-face culling", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxBackFaceCullingUrl, backFaceCulling: true, @@ -3690,7 +3793,7 @@ describe( }); it("disables back-face culling", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxBackFaceCullingUrl, backFaceCulling: false, @@ -3705,7 +3808,7 @@ describe( }); it("ignores back-face culling when translucent", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxBackFaceCullingUrl, backFaceCulling: true, @@ -3726,7 +3829,7 @@ describe( }); it("toggles back-face culling at runtime", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxBackFaceCullingUrl, backFaceCulling: false, @@ -3747,7 +3850,7 @@ describe( }); it("ignores back-face culling toggles when translucent", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxBackFaceCullingUrl, backFaceCulling: false, @@ -3776,7 +3879,7 @@ describe( }); it("reverses winding order for negatively scaled models", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, modelMatrix: Matrix4.fromUniformScale(-1.0), @@ -3815,12 +3918,12 @@ describe( const clippingPlanes = new ClippingPlaneCollection({ planes: [plane], }); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, clippingPlanes: clippingPlanes }, scene ) .then(function (model) { - return loadAndZoomToModel({ gltf: boxTexturedGlbUrl }, scene); + return loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene); }) .then(function (model2) { expect(function () { @@ -3834,7 +3937,7 @@ describe( const clippingPlanes = new ClippingPlaneCollection({ planes: [plane], }); - return loadAndZoomToModel({ gltf: boxTexturedGlbUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene).then( function (model) { const gl = scene.frameState.context._gl; spyOn(gl, "texImage2D").and.callThrough(); @@ -3860,7 +3963,7 @@ describe( const clippingPlanes = new ClippingPlaneCollection({ planes: [plane], }); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, clippingPlanes: clippingPlanes }, scene ).then(function (model) { @@ -3881,7 +3984,7 @@ describe( const clippingPlanes = new ClippingPlaneCollection({ planes: [plane], }); - return loadAndZoomToModel({ gltf: boxTexturedGlbUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene).then( function (model) { let modelColor; verifyRender(model, true); @@ -3909,7 +4012,7 @@ describe( const clippingPlanes = new ClippingPlaneCollection({ planes: [plane], }); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, clippingPlanes: clippingPlanes }, scene ).then(function (model) { @@ -3925,7 +4028,7 @@ describe( const clippingPlanes = new ClippingPlaneCollection({ planes: [plane], }); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, clippingPlanes: clippingPlanes }, scene ).then(function (model) { @@ -3944,7 +4047,7 @@ describe( planes: [modelClippedPlane], }); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, clippingPlanes: clippingPlanes }, scene ).then(function (model) { @@ -3972,7 +4075,7 @@ describe( edgeColor: Color.BLUE, }); - return loadAndZoomToModel({ gltf: boxTexturedGlbUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene).then( function (model) { let modelColor; verifyRender(model, true); @@ -4002,7 +4105,7 @@ describe( ], unionClippingRegions: true, }); - return loadAndZoomToModel({ gltf: boxTexturedGlbUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene).then( function (model) { verifyRender(model, true); @@ -4017,7 +4120,7 @@ describe( }); it("destroys attached ClippingPlaneCollections", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, }, @@ -4039,7 +4142,7 @@ describe( it("destroys ClippingPlaneCollections that are detached", function () { let clippingPlanes; - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGlbUrl, }, @@ -4058,7 +4161,7 @@ describe( }); it("renders with classificationType", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { url: boxTexturedGltfUrl, classificationType: ClassificationType.CESIUM_3D_TILE, @@ -4076,16 +4179,17 @@ describe( describe("statistics", function () { it("gets triangle count", function () { - return loadAndZoomToModel({ gltf: boxTexturedGltfUrl }, scene).then( - function (model) { - const statistics = model.statistics; - expect(statistics.trianglesLength).toEqual(12); - } - ); + return loadAndZoomToModelAsync( + { gltf: boxTexturedGltfUrl }, + scene + ).then(function (model) { + const statistics = model.statistics; + expect(statistics.trianglesLength).toEqual(12); + }); }); it("gets point count", function () { - return loadAndZoomToModel({ gltf: pointCloudUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: pointCloudUrl }, scene).then( function (model) { const statistics = model.statistics; expect(statistics.pointsLength).toEqual(2500); @@ -4094,7 +4198,7 @@ describe( }); it("gets memory usage for geometry and textures", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxTexturedGltfUrl, incrementallyLoadTextures: false }, scene ).then(function (model) { @@ -4109,7 +4213,7 @@ describe( }); it("gets memory usage for property tables", function () { - return loadAndZoomToModel({ gltf: buildingsMetadata }, scene).then( + return loadAndZoomToModelAsync({ gltf: buildingsMetadata }, scene).then( function (model) { const expectedPropertyTableMemory = 110; @@ -4123,8 +4227,8 @@ describe( }); describe("AGI_articulations", function () { - it("setArticulationStage throws when model is not ready", function () { - const model = Model.fromGltf({ + it("setArticulationStage throws when model is not ready", async function () { + const model = await Model.fromGltfAsync({ url: boxArticulationsUrl, }); @@ -4134,7 +4238,7 @@ describe( }); it("setArticulationStage throws with invalid value", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxArticulationsUrl, }, @@ -4146,8 +4250,8 @@ describe( }); }); - it("applyArticulations throws when model is not ready", function () { - const model = Model.fromGltf({ + it("applyArticulations throws when model is not ready", async function () { + const model = await Model.fromGltfAsync({ url: boxArticulationsUrl, }); @@ -4157,7 +4261,7 @@ describe( }); it("applies articulations", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxArticulationsUrl, }, @@ -4185,8 +4289,8 @@ describe( }); describe("getNode", function () { - it("getNode throws when model is not ready", function () { - const model = Model.fromGltf({ + it("getNode throws when model is not ready", async function () { + const model = await Model.fromGltfAsync({ url: boxArticulationsUrl, }); @@ -4196,7 +4300,7 @@ describe( }); it("getNode throws when name is undefined", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxArticulationsUrl, }, @@ -4209,7 +4313,7 @@ describe( }); it("getNode returns undefined for nonexistent node", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxArticulationsUrl, }, @@ -4221,7 +4325,7 @@ describe( }); it("getNode returns a node", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxArticulationsUrl, }, @@ -4239,7 +4343,7 @@ describe( }); it("changing node.show works", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxArticulationsUrl, }, @@ -4255,7 +4359,7 @@ describe( }); it("changing node.matrix works", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { gltf: boxArticulationsUrl, }, @@ -4276,7 +4380,7 @@ describe( it("destroy works", function () { spyOn(ShaderProgram.prototype, "destroy").and.callThrough(); - return loadAndZoomToModel({ gltf: boxTexturedGlbUrl }, scene).then( + return loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene).then( function (model) { const resources = model._pipelineResources; const loader = model._loader; @@ -4309,8 +4413,8 @@ describe( it("destroy doesn't destroy resources when they're in use", function () { return Promise.all([ - loadAndZoomToModel({ gltf: boxTexturedGlbUrl }, scene), - loadAndZoomToModel({ gltf: boxTexturedGlbUrl }, scene), + loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene), + loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene), ]).then(function (models) { const cacheEntries = ResourceCache.cacheEntries; let cacheKey; diff --git a/packages/engine/Specs/Scene/Model/loadAndZoomToModel.js b/packages/engine/Specs/Scene/Model/loadAndZoomToModel.js deleted file mode 100644 index 833f1e9f9e49..000000000000 --- a/packages/engine/Specs/Scene/Model/loadAndZoomToModel.js +++ /dev/null @@ -1,86 +0,0 @@ -import { Model } from "../../../index.js"; -import pollToPromise from "../../../../../Specs/pollToPromise.js"; - -function loadAndZoomToModel(options, scene) { - return new Promise(function (resolve, reject) { - let model; - try { - model = Model.fromGltf({ - url: options.url, - gltf: options.gltf, - basePath: options.basePath, - show: options.show, - modelMatrix: options.modelMatrix, - scale: options.scale, - minimumPixelSize: options.minimumPixelSize, - maximumScale: options.maximumScale, - id: options.id, - allowPicking: options.allowPicking, - incrementallyLoadTextures: options.incrementallyLoadTextures, - asynchronous: options.asynchronous, - clampAnimations: options.clampAnimations, - shadows: options.shadows, - debugShowBoundingVolume: options.debugShowBoundingVolume, - enableDebugWireframe: options.enableDebugWireframe, - debugWireframe: options.debugWireframe, - cull: options.cull, - opaquePass: options.opaquePass, - upAxis: options.upAxis, - forwardAxis: options.forwardAxis, - customShader: options.customShader, - content: options.content, - heightReference: options.heightReference, - scene: options.scene, - distanceDisplayCondition: options.distanceDisplayCondition, - color: options.color, - colorBlendAmount: options.colorBlendAmount, - colorBlendMode: options.colorBlendMode, - silhouetteColor: options.silhouetteColor, - silhouetteSize: options.silhouetteSize, - clippingPlanes: options.clippingPlanes, - lightColor: options.lightColor, - imageBasedLighting: options.imageBasedLighting, - backFaceCulling: options.backFaceCulling, - credit: options.credit, - showCreditsOnScreen: options.showCreditsOnScreen, - projectTo2D: options.projectTo2D, - featureIdLabel: options.featureIdLabel, - instanceFeatureIdLabel: options.instanceFeatureIdLabel, - classificationType: options.classificationType, - }); - } catch (error) { - reject(error); - return; - } - - scene.primitives.add(model); - - let finished = false; - model.readyPromise - .then(function (model) { - finished = true; - scene.camera.flyToBoundingSphere(model.boundingSphere, { - duration: 0, - offset: options.offset, - }); - - resolve(model); - }) - .catch(function (error) { - finished = true; - reject(error); - }); - - pollToPromise( - function () { - scene.renderForSpecs(); - return finished; - }, - { timeout: 10000 } - ).catch(function (error) { - reject(error); - }); - }); -} - -export default loadAndZoomToModel; diff --git a/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js b/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js new file mode 100644 index 000000000000..1c645260b02d --- /dev/null +++ b/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js @@ -0,0 +1,67 @@ +import { Model } from "../../../index.js"; +import pollToPromise from "../../../../../Specs/pollToPromise.js"; + +async function loadAndZoomToModelAsync(options, scene) { + const model = await Model.fromGltfAsync({ + url: options.url, + gltf: options.gltf, + basePath: options.basePath, + show: options.show, + modelMatrix: options.modelMatrix, + scale: options.scale, + minimumPixelSize: options.minimumPixelSize, + maximumScale: options.maximumScale, + id: options.id, + allowPicking: options.allowPicking, + incrementallyLoadTextures: options.incrementallyLoadTextures, + asynchronous: options.asynchronous, + clampAnimations: options.clampAnimations, + shadows: options.shadows, + debugShowBoundingVolume: options.debugShowBoundingVolume, + enableDebugWireframe: options.enableDebugWireframe, + debugWireframe: options.debugWireframe, + cull: options.cull, + opaquePass: options.opaquePass, + upAxis: options.upAxis, + forwardAxis: options.forwardAxis, + customShader: options.customShader, + content: options.content, + heightReference: options.heightReference, + scene: options.scene, + distanceDisplayCondition: options.distanceDisplayCondition, + color: options.color, + colorBlendAmount: options.colorBlendAmount, + colorBlendMode: options.colorBlendMode, + silhouetteColor: options.silhouetteColor, + silhouetteSize: options.silhouetteSize, + clippingPlanes: options.clippingPlanes, + lightColor: options.lightColor, + imageBasedLighting: options.imageBasedLighting, + backFaceCulling: options.backFaceCulling, + credit: options.credit, + showCreditsOnScreen: options.showCreditsOnScreen, + projectTo2D: options.projectTo2D, + featureIdLabel: options.featureIdLabel, + instanceFeatureIdLabel: options.instanceFeatureIdLabel, + classificationType: options.classificationType, + }); + + scene.primitives.add(model); + + await pollToPromise( + function () { + scene.renderForSpecs(); + return model.ready; + }, + { timeout: 10000 } + ); + + scene.camera.flyToBoundingSphere(model.boundingSphere, { + duration: 0, + offset: options.offset, + }); + + return model; +} + +export default loadAndZoomToModelAsync; diff --git a/packages/engine/Specs/Scene/PostProcessStageLibrarySpec.js b/packages/engine/Specs/Scene/PostProcessStageLibrarySpec.js index 886e90aa927f..7f398be1e771 100644 --- a/packages/engine/Specs/Scene/PostProcessStageLibrarySpec.js +++ b/packages/engine/Specs/Scene/PostProcessStageLibrarySpec.js @@ -10,7 +10,7 @@ import createCanvas from "../../../../Specs/createCanvas.js"; import createScene from "../../../../Specs/createScene.js"; import pollToPromise from "../../../../Specs/pollToPromise.js"; import ViewportPrimitive from "../../../../Specs/ViewportPrimitive.js"; -import loadAndZoomToModel from "./Model/loadAndZoomToModel.js"; +import loadAndZoomToModelAsync from "./Model/loadAndZoomToModelAsync.js"; describe( "Scene/PostProcessStageLibrary", @@ -87,7 +87,7 @@ describe( }); it("per-feature black and white", function () { - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { url: boxTexturedUrl, }, @@ -305,7 +305,7 @@ describe( new HeadingPitchRoll() ); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { url: boxTexturedUrl, // Ensure the texture loads every time @@ -426,7 +426,7 @@ describe( new HeadingPitchRoll() ); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { url: boxTexturedUrl, // Ensure the texture loads every time diff --git a/packages/engine/Specs/Scene/PostProcessStageSpec.js b/packages/engine/Specs/Scene/PostProcessStageSpec.js index 1dd5b786a7cc..486f528d1b6b 100644 --- a/packages/engine/Specs/Scene/PostProcessStageSpec.js +++ b/packages/engine/Specs/Scene/PostProcessStageSpec.js @@ -12,7 +12,7 @@ import { import createScene from "../../../../Specs/createScene.js"; import pollToPromise from "../../../../Specs/pollToPromise.js"; -import loadAndZoomToModel from "./Model/loadAndZoomToModel.js"; +import loadAndZoomToModelAsync from "./Model/loadAndZoomToModelAsync.js"; describe( "Scene/PostProcessStage", @@ -239,7 +239,7 @@ describe( // the camera just a little const offset = new HeadingPitchRange(0, -CesiumMath.PI_OVER_FOUR, 2); - return loadAndZoomToModel( + return loadAndZoomToModelAsync( { url: "./Data/Models/glTF-2.0/BoxTextured/glTF/BoxTextured.gltf", offset: offset, diff --git a/packages/engine/Specs/Scene/ResourceCacheSpec.js b/packages/engine/Specs/Scene/ResourceCacheSpec.js index b476fd69e019..aaded214a56a 100644 --- a/packages/engine/Specs/Scene/ResourceCacheSpec.js +++ b/packages/engine/Specs/Scene/ResourceCacheSpec.js @@ -1,7 +1,12 @@ import { - ComponentDatatype, - DracoLoader, + BufferLoader, + GltfBufferViewLoader, + GltfDracoLoader, + GltfImageLoader, + GltfIndexBufferLoader, GltfJsonLoader, + GltfTextureLoader, + GltfVertexBufferLoader, MetadataSchemaLoader, Resource, ResourceCache, @@ -10,1286 +15,946 @@ import { } from "../../index.js"; import concatTypedArrays from "../../../../Specs/concatTypedArrays.js"; import createScene from "../../../../Specs/createScene.js"; -import generateJsonBuffer from "../../../../Specs/generateJsonBuffer.js"; -import waitForLoaderProcess from "../../../../Specs/waitForLoaderProcess.js"; - -describe( - "ResourceCache", - function () { - const schemaResource = new Resource({ - url: "https://example.com/schema.json", - }); - const schemaJson = {}; - - const bufferParentResource = new Resource({ - url: "https://example.com/model.glb", - }); - const bufferResource = new Resource({ - url: "https://example.com/external.bin", - }); - - const gltfUri = "https://example.com/model.glb"; - const gltfResource = new Resource({ - url: gltfUri, - }); - - const image = new Image(); - image.src = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII="; - - const positions = new Float32Array([-1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0]); // prettier-ignore - const normals = new Float32Array([-1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]); // prettier-ignore - const indices = new Uint16Array([0, 1, 2]); - - const bufferTypedArray = concatTypedArrays([positions, normals, indices]); - const bufferArrayBuffer = bufferTypedArray.buffer; - - const dracoBufferTypedArray = new Uint8Array([ - 1, - 3, - 7, - 15, - 31, - 63, - 127, - 255, - ]); - const dracoArrayBuffer = dracoBufferTypedArray.buffer; - - const decodedPositions = new Uint16Array([0, 0, 0, 65535, 65535, 65535, 0, 65535, 0]); // prettier-ignore - const decodedNormals = new Uint8Array([0, 255, 128, 128, 255, 0]); - const decodedIndices = new Uint16Array([0, 1, 2]); - - const decodeDracoResults = { - indexArray: { - typedArray: decodedIndices, - numberOfIndices: decodedIndices.length, +describe("ResourceCache", function () { + const schemaResource = new Resource({ + url: "https://example.com/schema.json", + }); + const schemaJson = {}; + + const bufferParentResource = new Resource({ + url: "https://example.com/model.glb", + }); + const bufferResource = new Resource({ + url: "https://example.com/external.bin", + }); + + const gltfUri = "https://example.com/model.glb"; + + const gltfResource = new Resource({ + url: gltfUri, + }); + + const image = new Image(); + image.src = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII="; + + const positions = new Float32Array([-1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0]); // prettier-ignore + const normals = new Float32Array([-1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]); // prettier-ignore + const indices = new Uint16Array([0, 1, 2]); + + const bufferTypedArray = concatTypedArrays([positions, normals, indices]); + const gltfDraco = { + buffers: [ + { + uri: "external.bin", + byteLength: 8, + }, + ], + bufferViews: [ + { + buffer: 0, + byteOffset: 4, + byteLength: 4, + }, + ], + accessors: [ + { + componentType: 5126, + count: 3, + max: [-1.0, -1.0, -1.0], + min: [1.0, 1.0, 1.0], + type: "VEC3", + }, + { + componentType: 5126, + count: 3, + type: "VEC3", }, - attributeData: { - POSITION: { - array: decodedPositions, - data: { - byteOffset: 0, - byteStride: 6, - componentDatatype: ComponentDatatype.UNSIGNED_SHORT, - componentsPerAttribute: 3, - normalized: false, - quantization: { - octEncoded: false, - quantizationBits: 14, - minValues: [-1.0, -1.0, -1.0], - range: 2.0, + { + componentType: 5123, + count: 3, + type: "SCALAR", + }, + ], + meshes: [ + { + primitives: [ + { + attributes: { + POSITION: 0, + NORMAL: 1, + }, + indices: 2, + extensions: { + KHR_draco_mesh_compression: { + bufferView: 0, + attributes: { + POSITION: 0, + NORMAL: 1, + }, + }, }, }, - }, - NORMAL: { - array: decodedNormals, - data: { - byteOffset: 0, - byteStride: 2, - componentDatatype: ComponentDatatype.UNSIGNED_BYTE, - componentsPerAttribute: 2, - normalized: false, - quantization: { - octEncoded: true, - quantizationBits: 10, + ], + }, + ], + }; + + const dracoExtension = + gltfDraco.meshes[0].primitives[0].extensions.KHR_draco_mesh_compression; + + const gltfUncompressed = { + buffers: [ + { + uri: "external.bin", + byteLength: 78, + }, + ], + bufferViews: [ + { + buffer: 0, + byteOffset: 0, + byteLength: 36, + }, + { + buffer: 0, + byteOffset: 36, + byteLength: 36, + }, + { + buffer: 0, + byteOffset: 72, + byteLength: 6, + }, + ], + accessors: [ + { + componentType: 5126, + count: 3, + max: [-1.0, -1.0, -1.0], + min: [1.0, 1.0, 1.0], + type: "VEC3", + bufferView: 0, + byteOffset: 0, + }, + { + componentType: 5126, + count: 3, + type: "VEC3", + bufferView: 1, + byteOffset: 0, + }, + { + componentType: 5123, + count: 3, + type: "SCALAR", + bufferView: 2, + byteOffset: 0, + }, + ], + meshes: [ + { + primitives: [ + { + attributes: { + POSITION: 0, + NORMAL: 1, }, + indices: 2, }, - }, + ], }, - }; + ], + }; - const gltfDraco = { - buffers: [ - { - uri: "external.bin", - byteLength: 8, - }, - ], - bufferViews: [ - { - buffer: 0, - byteOffset: 4, - byteLength: 4, - }, - ], - accessors: [ - { - componentType: 5126, - count: 3, - max: [-1.0, -1.0, -1.0], - min: [1.0, 1.0, 1.0], - type: "VEC3", - }, - { - componentType: 5126, - count: 3, - type: "VEC3", - }, - { - componentType: 5123, - count: 3, - type: "SCALAR", + const gltfWithTextures = { + images: [ + { + uri: "image.png", + }, + ], + textures: [ + { + source: 0, + }, + ], + materials: [ + { + emissiveTexture: { + index: 0, }, - ], - meshes: [ - { - primitives: [ - { - attributes: { - POSITION: 0, - NORMAL: 1, - }, - indices: 2, - extensions: { - KHR_draco_mesh_compression: { - bufferView: 0, - attributes: { - POSITION: 0, - NORMAL: 1, - }, - }, - }, - }, - ], + occlusionTexture: { + index: 1, }, - ], - }; + }, + ], + }; - const dracoExtension = - gltfDraco.meshes[0].primitives[0].extensions.KHR_draco_mesh_compression; + const mockFrameState = { + context: { + id: "01234", + }, + }; - const gltfUncompressed = { - buffers: [ - { - uri: "external.bin", - byteLength: 78, - }, - ], - bufferViews: [ - { - buffer: 0, - byteOffset: 0, - byteLength: 36, - }, - { - buffer: 0, - byteOffset: 36, - byteLength: 36, - }, - { - buffer: 0, - byteOffset: 72, - byteLength: 6, - }, - ], - accessors: [ - { - componentType: 5126, - count: 3, - max: [-1.0, -1.0, -1.0], - min: [1.0, 1.0, 1.0], - type: "VEC3", - bufferView: 0, - byteOffset: 0, - }, - { - componentType: 5126, - count: 3, - type: "VEC3", - bufferView: 1, - byteOffset: 0, - }, - { - componentType: 5123, - count: 3, - type: "SCALAR", - bufferView: 2, - byteOffset: 0, - }, - ], - meshes: [ - { - primitives: [ - { - attributes: { - POSITION: 0, - NORMAL: 1, - }, - indices: 2, - }, - ], - }, - ], - }; + const mockFrameState2 = { + context: { + id: "56789", + }, + }; - const gltfWithTextures = { - images: [ - { - uri: "image.png", - }, - ], - textures: [ - { - source: 0, - }, - ], - materials: [ - { - emissiveTexture: { - index: 0, - }, - occlusionTexture: { - index: 1, - }, - }, - ], - }; + let scene; - const mockFrameState = { - context: { - id: "01234", - }, - }; + beforeAll(function () { + scene = createScene(); + }); - const mockFrameState2 = { - context: { - id: "56789", - }, - }; + afterAll(function () { + scene.destroyForSpecs(); + }); - let scene; + afterEach(function () { + ResourceCache.clearForSpecs(); + }); - beforeAll(function () { - scene = createScene(); + it("adds resource", function () { + const cacheKey = ResourceCacheKey.getSchemaCacheKey({ + resource: schemaResource, }); - - afterAll(function () { - scene.destroyForSpecs(); + const resourceLoader = new MetadataSchemaLoader({ + resource: schemaResource, + cacheKey: cacheKey, }); - afterEach(function () { - ResourceCache.clearForSpecs(); + ResourceCache.add(resourceLoader); + + const cacheEntry = ResourceCache.cacheEntries[cacheKey]; + expect(cacheEntry.referenceCount).toBe(1); + expect(cacheEntry.resourceLoader).toBe(resourceLoader); + }); + + it("add throws if resourceLoader is undefined", function () { + expect(() => ResourceCache.add()).toThrowDeveloperError(); + }); + + it("add throws if resourceLoader's cacheKey is undefined", function () { + const resourceLoader = new MetadataSchemaLoader({ + resource: schemaResource, }); - it("loads resource", function () { - const fetchJson = spyOn(Resource.prototype, "fetchJson").and.returnValue( - Promise.resolve(schemaJson) - ); - - const cacheKey = ResourceCacheKey.getSchemaCacheKey({ - resource: schemaResource, - }); - const resourceLoader = new MetadataSchemaLoader({ - resource: schemaResource, - cacheKey: cacheKey, - }); - - ResourceCache.load({ - resourceLoader: resourceLoader, - }); - - const cacheEntry = ResourceCache.cacheEntries[cacheKey]; - expect(cacheEntry.referenceCount).toBe(1); - expect(cacheEntry.resourceLoader).toBe(resourceLoader); - - return resourceLoader.promise.then(function (resourceLoader) { - expect(fetchJson).toHaveBeenCalled(); - - const schema = resourceLoader.schema; - expect(schema).toBeDefined(); - }); + expect(() => ResourceCache.add(resourceLoader)).toThrowDeveloperError(); + }); + + it("add throws if a resource with this cacheKey is already in the cache", function () { + const cacheKey = ResourceCacheKey.getSchemaCacheKey({ + resource: schemaResource, }); - it("load throws if resourceLoader is undefined", function () { - expect(function () { - ResourceCache.load({ - resourceLoader: undefined, - }); - }).toThrowDeveloperError(); + const resourceLoader = new MetadataSchemaLoader({ + resource: schemaResource, + cacheKey: cacheKey, }); - it("load throws if resourceLoader's cacheKey is undefined", function () { - const resourceLoader = new MetadataSchemaLoader({ - resource: schemaResource, - }); + ResourceCache.add(resourceLoader); - expect(function () { - ResourceCache.load({ - resourceLoader: resourceLoader, - }); - }).toThrowDeveloperError(); - }); + expect(() => ResourceCache.add(resourceLoader)).toThrowDeveloperError(); + }); - it("load throws if a resource with this cacheKey is already in the cache", function () { - const cacheKey = ResourceCacheKey.getSchemaCacheKey({ - resource: schemaResource, - }); - - const resourceLoader = new MetadataSchemaLoader({ - resource: schemaResource, - cacheKey: cacheKey, - }); - - ResourceCache.load({ - resourceLoader: resourceLoader, - }); - - expect(function () { - ResourceCache.load({ - resourceLoader: resourceLoader, - }); - }).toThrowDeveloperError(); + it("destroys resource when reference count reaches 0", function () { + const destroy = spyOn( + MetadataSchemaLoader.prototype, + "destroy" + ).and.callThrough(); + + const cacheKey = ResourceCacheKey.getSchemaCacheKey({ + resource: schemaResource, + }); + const resourceLoader = new MetadataSchemaLoader({ + resource: schemaResource, + cacheKey: cacheKey, }); - it("destroys resource when reference count reaches 0", function () { - spyOn(Resource.prototype, "fetchJson").and.returnValue( - Promise.resolve(schemaJson) - ); + ResourceCache.add(resourceLoader); - const destroy = spyOn( - MetadataSchemaLoader.prototype, - "destroy" - ).and.callThrough(); + ResourceCache.get(cacheKey); - const cacheKey = ResourceCacheKey.getSchemaCacheKey({ - resource: schemaResource, - }); - const resourceLoader = new MetadataSchemaLoader({ - resource: schemaResource, - cacheKey: cacheKey, - }); + const cacheEntry = ResourceCache.cacheEntries[cacheKey]; + expect(cacheEntry.referenceCount).toBe(2); - ResourceCache.load({ - resourceLoader: resourceLoader, - }); + ResourceCache.unload(resourceLoader); + expect(cacheEntry.referenceCount).toBe(1); + expect(destroy).not.toHaveBeenCalled(); - ResourceCache.get(cacheKey); + ResourceCache.unload(resourceLoader); + expect(cacheEntry.referenceCount).toBe(0); + expect(destroy).toHaveBeenCalled(); + expect(ResourceCache.cacheEntries[cacheKey]).toBeUndefined(); + }); - const cacheEntry = ResourceCache.cacheEntries[cacheKey]; - expect(cacheEntry.referenceCount).toBe(2); + it("unload throws if resourceLoader is undefined", function () { + expect(function () { + ResourceCache.unload(); + }).toThrowDeveloperError(); + }); - ResourceCache.unload(resourceLoader); - expect(cacheEntry.referenceCount).toBe(1); - expect(destroy).not.toHaveBeenCalled(); + it("unload throws if resourceLoader is not in the cache", function () { + const cacheKey = ResourceCacheKey.getSchemaCacheKey({ + resource: schemaResource, + }); + const resourceLoader = new MetadataSchemaLoader({ + resource: schemaResource, + cacheKey: cacheKey, + }); + expect(function () { ResourceCache.unload(resourceLoader); - expect(cacheEntry.referenceCount).toBe(0); - expect(destroy).toHaveBeenCalled(); - expect(ResourceCache.cacheEntries[cacheKey]).toBeUndefined(); - }); + }).toThrowDeveloperError(); + }); - it("unload throws if resourceLoader is undefined", function () { - expect(function () { - ResourceCache.unload(); - }).toThrowDeveloperError(); + it("unload throws if resourceLoader has already been unloaded from the cache", function () { + const cacheKey = ResourceCacheKey.getSchemaCacheKey({ + resource: schemaResource, }); - - it("unload throws if resourceLoader is not in the cache", function () { - const cacheKey = ResourceCacheKey.getSchemaCacheKey({ - resource: schemaResource, - }); - const resourceLoader = new MetadataSchemaLoader({ - resource: schemaResource, - cacheKey: cacheKey, - }); - - expect(function () { - ResourceCache.unload(resourceLoader); - }).toThrowDeveloperError(); + const resourceLoader = new MetadataSchemaLoader({ + resource: schemaResource, + cacheKey: cacheKey, }); - it("unload throws if resourceLoader has already been unloaded from the cache", function () { - spyOn(Resource.prototype, "fetchJson").and.returnValue( - Promise.resolve(schemaJson) - ); - - const cacheKey = ResourceCacheKey.getSchemaCacheKey({ - resource: schemaResource, - }); - const resourceLoader = new MetadataSchemaLoader({ - resource: schemaResource, - cacheKey: cacheKey, - }); + ResourceCache.add(resourceLoader); - ResourceCache.load({ - resourceLoader: resourceLoader, - }); + ResourceCache.unload(resourceLoader); + expect(function () { ResourceCache.unload(resourceLoader); + }).toThrowDeveloperError(); + }); - expect(function () { - ResourceCache.unload(resourceLoader); - }).toThrowDeveloperError(); + it("gets resource", function () { + const cacheKey = ResourceCacheKey.getSchemaCacheKey({ + resource: schemaResource, + }); + const resourceLoader = new MetadataSchemaLoader({ + resource: schemaResource, + cacheKey: cacheKey, }); - it("gets resource", function () { - spyOn(Resource.prototype, "fetchJson").and.returnValue( - Promise.resolve(schemaJson) - ); - - const cacheKey = ResourceCacheKey.getSchemaCacheKey({ - resource: schemaResource, - }); - const resourceLoader = new MetadataSchemaLoader({ - resource: schemaResource, - cacheKey: cacheKey, - }); + ResourceCache.add(resourceLoader); - ResourceCache.load({ - resourceLoader: resourceLoader, - }); + const cacheEntry = ResourceCache.cacheEntries[cacheKey]; - const cacheEntry = ResourceCache.cacheEntries[cacheKey]; + ResourceCache.get(cacheKey); + expect(cacheEntry.referenceCount).toBe(2); - ResourceCache.get(cacheKey); - expect(cacheEntry.referenceCount).toBe(2); + ResourceCache.get(cacheKey); + expect(cacheEntry.referenceCount).toBe(3); + }); - ResourceCache.get(cacheKey); - expect(cacheEntry.referenceCount).toBe(3); + it("get returns undefined if there is no resource with this cache key", function () { + const cacheKey = ResourceCacheKey.getSchemaCacheKey({ + resource: schemaResource, }); - it("get returns undefined if there is no resource with this cache key", function () { - const cacheKey = ResourceCacheKey.getSchemaCacheKey({ - resource: schemaResource, - }); + expect(ResourceCache.get(cacheKey)).toBeUndefined(); + }); - expect(ResourceCache.get(cacheKey)).toBeUndefined(); + it("gets schema loader", function () { + const expectedCacheKey = ResourceCacheKey.getSchemaCacheKey({ + schema: schemaJson, }); - - it("loads schema from JSON", function () { - const expectedCacheKey = ResourceCacheKey.getSchemaCacheKey({ - schema: schemaJson, - }); - const schemaLoader = ResourceCache.loadSchema({ - schema: schemaJson, - }); - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(schemaLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadSchema({ - schema: schemaJson, - }) - ).toBe(schemaLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return schemaLoader.promise.then(function (schemaLoader) { - const schema = schemaLoader.schema; - expect(schema).toBeDefined(); - }); - }); - - it("loads external schema", function () { - spyOn(Resource.prototype, "fetchJson").and.returnValue( - Promise.resolve(schemaJson) - ); - - const expectedCacheKey = ResourceCacheKey.getSchemaCacheKey({ - resource: schemaResource, - }); - const schemaLoader = ResourceCache.loadSchema({ - resource: schemaResource, - }); - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(schemaLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadSchema({ - resource: schemaResource, - }) - ).toBe(schemaLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return schemaLoader.promise.then(function (schemaLoader) { - const schema = schemaLoader.schema; - expect(schema).toBeDefined(); - }); + const schemaLoader = ResourceCache.getSchemaLoader({ + schema: schemaJson, }); + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(schemaLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(schemaLoader).toBeInstanceOf(MetadataSchemaLoader); - it("loadSchema throws if neither options.schema nor options.resource are defined", function () { - expect(function () { - ResourceCache.loadSchema({ - schema: undefined, - resource: undefined, - }); - }).toThrowDeveloperError(); - }); + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getSchemaLoader({ + schema: schemaJson, + }) + ).toBe(schemaLoader); + + expect(cacheEntry.referenceCount).toBe(2); + }); + + it("gets embedded buffer loader", function () { + const expectedCacheKey = ResourceCacheKey.getEmbeddedBufferCacheKey({ + parentResource: bufferParentResource, + bufferId: 0, + }); + const bufferLoader = ResourceCache.getEmbeddedBufferLoader({ + parentResource: bufferParentResource, + bufferId: 0, + typedArray: bufferTypedArray, + }); + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(bufferLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(bufferLoader).toBeInstanceOf(BufferLoader); + + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getEmbeddedBufferLoader({ + parentResource: bufferParentResource, + bufferId: 0, + typedArray: bufferTypedArray, + }) + ).toBe(bufferLoader); - it("loadSchema throws if both options.schema and options.resource are defined", function () { - expect(function () { - ResourceCache.loadSchema({ - schema: schemaJson, - resource: schemaResource, - }); - }).toThrowDeveloperError(); - }); + expect(cacheEntry.referenceCount).toBe(2); + }); - it("loads embedded buffer", function () { - const expectedCacheKey = ResourceCacheKey.getEmbeddedBufferCacheKey({ - parentResource: bufferParentResource, + it("getEmbeddedBufferLoader throws if parentResource is undefined", function () { + expect(() => + ResourceCache.getEmbeddedBufferLoader({ bufferId: 0, - }); - const bufferLoader = ResourceCache.loadEmbeddedBuffer({ + typedArray: bufferTypedArray, + }) + ).toThrowDeveloperError(); + }); + + it("getEmbeddedBufferLoader throws if bufferId is undefined", function () { + expect(() => + ResourceCache.getEmbeddedBufferLoader({ parentResource: bufferParentResource, - bufferId: 0, typedArray: bufferTypedArray, - }); - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(bufferLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadEmbeddedBuffer({ - parentResource: bufferParentResource, - bufferId: 0, - typedArray: bufferTypedArray, - }) - ).toBe(bufferLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return bufferLoader.promise.then(function (bufferLoader) { - expect(bufferLoader.typedArray).toBe(bufferTypedArray); - }); - }); + }) + ).toThrowDeveloperError(); + }); - it("loadEmbeddedBuffer throws if parentResource is undefined", function () { - expect(function () { - ResourceCache.loadEmbeddedBuffer({ - bufferId: 0, - typedArray: bufferTypedArray, - }); - }).toThrowDeveloperError(); - }); + it("getEmbeddedBufferLoader throws if typedArray is undefined", function () { + expect(() => + ResourceCache.getEmbeddedBufferLoader({ + parentResource: bufferParentResource, + bufferId: 0, + }) + ).toThrowDeveloperError(); + }); - it("loadEmbeddedBuffer throws if bufferId is undefined", function () { - expect(function () { - ResourceCache.loadEmbeddedBuffer({ - parentResource: bufferParentResource, - typedArray: bufferTypedArray, - }); - }).toThrowDeveloperError(); + it("gets external buffer loader", function () { + const expectedCacheKey = ResourceCacheKey.getExternalBufferCacheKey({ + resource: bufferResource, }); - - it("loadEmbeddedBuffer throws if typedArray is undefined", function () { - expect(function () { - ResourceCache.loadEmbeddedBuffer({ - parentResource: bufferParentResource, - bufferId: 0, - }); - }).toThrowDeveloperError(); + const bufferLoader = ResourceCache.getExternalBufferLoader({ + resource: bufferResource, }); + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(bufferLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(bufferLoader).toBeInstanceOf(BufferLoader); - it("loads external buffer", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(bufferArrayBuffer) - ); - - const expectedCacheKey = ResourceCacheKey.getExternalBufferCacheKey({ - resource: bufferResource, - }); - const bufferLoader = ResourceCache.loadExternalBuffer({ + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getExternalBufferLoader({ resource: bufferResource, - }); - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(bufferLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadExternalBuffer({ - resource: bufferResource, - }) - ).toBe(bufferLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return bufferLoader.promise.then(function (bufferLoader) { - expect(bufferLoader.typedArray.buffer).toBe(bufferArrayBuffer); - }); - }); - - it("loadExternalBuffer throws if resource is undefined", function () { - expect(function () { - ResourceCache.loadExternalBuffer({ - resource: undefined, - }); - }).toThrowDeveloperError(); - }); + }) + ).toBe(bufferLoader); + + expect(cacheEntry.referenceCount).toBe(2); + }); + + it("getExternalBufferLoader throws if resource is undefined", function () { + expect(() => + ResourceCache.getExternalBufferLoader({ + resource: undefined, + }) + ).toThrowDeveloperError(); + }); + + it("gets glTF loader", function () { + const expectedCacheKey = ResourceCacheKey.getGltfCacheKey({ + gltfResource: gltfResource, + }); + const gltfJsonLoader = ResourceCache.getGltfJsonLoader({ + gltfResource: gltfResource, + baseResource: gltfResource, + }); + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(gltfJsonLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(gltfJsonLoader).toBeInstanceOf(GltfJsonLoader); + + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getGltfJsonLoader({ + gltfResource: gltfResource, + baseResource: gltfResource, + }) + ).toBe(gltfJsonLoader); - it("loads glTF", function () { - const gltf = { - asset: { - version: "2.0", - }, - }; - const arrayBuffer = generateJsonBuffer(gltf).buffer; + expect(cacheEntry.referenceCount).toBe(2); + }); - spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( - Promise.resolve(arrayBuffer) - ); + it("getGltfJsonLoader throws if gltfResource is undefined", function () { + expect(() => + ResourceCache.getGltfJsonLoader({ + gltfResource: undefined, + baseResource: gltfResource, + }) + ).toThrowDeveloperError(); + }); - const expectedCacheKey = ResourceCacheKey.getGltfCacheKey({ + it("loadGltfJson throws if gltfResource is undefined", function () { + expect(() => + ResourceCache.getGltfJsonLoader({ gltfResource: gltfResource, - }); - const gltfJsonLoader = ResourceCache.loadGltfJson({ + baseResource: undefined, + }) + ).toThrowDeveloperError(); + }); + + it("gets buffer view loader", function () { + const expectedCacheKey = ResourceCacheKey.getBufferViewCacheKey({ + gltf: gltfUncompressed, + bufferViewId: 0, + gltfResource: gltfResource, + baseResource: gltfResource, + }); + const bufferViewLoader = ResourceCache.getBufferViewLoader({ + gltf: gltfUncompressed, + bufferViewId: 0, + gltfResource: gltfResource, + baseResource: gltfResource, + }); + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(bufferViewLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(bufferViewLoader).toBeInstanceOf(GltfBufferViewLoader); + + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getBufferViewLoader({ + gltf: gltfUncompressed, + bufferViewId: 0, gltfResource: gltfResource, baseResource: gltfResource, - }); - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(gltfJsonLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadGltfJson({ - gltfResource: gltfResource, - baseResource: gltfResource, - }) - ).toBe(gltfJsonLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return gltfJsonLoader.promise.then(function (gltfJsonLoader) { - expect(gltfJsonLoader.gltf).toBeDefined(); - }); - }); + }) + ).toBe(bufferViewLoader); - it("loadGltfJson throws if gltfResource is undefined", function () { - expect(function () { - ResourceCache.loadGltfJson({ - gltfResource: undefined, - baseResource: gltfResource, - }); - }).toThrowDeveloperError(); - }); + expect(cacheEntry.referenceCount).toBe(2); + }); - it("loadGltfJson throws if gltfResource is undefined", function () { - expect(function () { - ResourceCache.loadGltfJson({ - gltfResource: gltfResource, - baseResource: undefined, - }); - }).toThrowDeveloperError(); - }); + it("getBufferViewLoader throws if gltf is undefined", function () { + expect(() => + ResourceCache.getBufferViewLoader({ + gltf: undefined, + bufferViewId: 0, + gltfResource: gltfResource, + baseResource: gltfResource, + }) + ).toThrowDeveloperError(); + }); - it("loads buffer view", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(bufferArrayBuffer) - ); + it("getBufferViewLoader throws if bufferViewId is undefined", function () { + expect(() => + ResourceCache.getBufferViewLoader({ + gltf: gltfUncompressed, + bufferViewId: undefined, + gltfResource: gltfResource, + baseResource: gltfResource, + }) + ).toThrowDeveloperError(); + }); - const expectedCacheKey = ResourceCacheKey.getBufferViewCacheKey({ + it("getBufferViewLoader throws if gltfResource is undefined", function () { + expect(() => + ResourceCache.getBufferViewLoader({ gltf: gltfUncompressed, bufferViewId: 0, - gltfResource: gltfResource, + gltfResource: undefined, baseResource: gltfResource, - }); - const bufferViewLoader = ResourceCache.loadBufferView({ + }) + ).toThrowDeveloperError(); + }); + + it("getBufferViewLoader throws if baseResource is undefined", function () { + expect(() => + ResourceCache.getBufferViewLoader({ gltf: gltfUncompressed, bufferViewId: 0, gltfResource: gltfResource, + baseResource: undefined, + }) + ).toThrowDeveloperError(); + }); + + it("gets draco loader", function () { + const expectedCacheKey = ResourceCacheKey.getDracoCacheKey({ + gltf: gltfDraco, + draco: dracoExtension, + gltfResource: gltfResource, + baseResource: gltfResource, + }); + const dracoLoader = ResourceCache.getDracoLoader({ + gltf: gltfDraco, + draco: dracoExtension, + gltfResource: gltfResource, + baseResource: gltfResource, + }); + + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(dracoLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(dracoLoader).toBeInstanceOf(GltfDracoLoader); + + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getDracoLoader({ + gltf: gltfDraco, + draco: dracoExtension, + gltfResource: gltfResource, baseResource: gltfResource, - }); - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(bufferViewLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadBufferView({ - gltf: gltfUncompressed, - bufferViewId: 0, - gltfResource: gltfResource, - baseResource: gltfResource, - }) - ).toBe(bufferViewLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return bufferViewLoader.promise.then(function (bufferViewLoader) { - expect(bufferViewLoader.typedArray).toBeDefined(); - }); - }); - - it("loadBufferView throws if gltf is undefined", function () { - expect(function () { - ResourceCache.loadBufferView({ - gltf: undefined, - bufferViewId: 0, - gltfResource: gltfResource, - baseResource: gltfResource, - }); - }).toThrowDeveloperError(); - }); + }) + ).toBe(dracoLoader); - it("loadBufferView throws if bufferViewId is undefined", function () { - expect(function () { - ResourceCache.loadBufferView({ - gltf: gltfUncompressed, - bufferViewId: undefined, - gltfResource: gltfResource, - baseResource: gltfResource, - }); - }).toThrowDeveloperError(); - }); - - it("loadBufferView throws if gltfResource is undefined", function () { - expect(function () { - ResourceCache.loadBufferView({ - gltf: gltfUncompressed, - bufferViewId: 0, - gltfResource: undefined, - baseResource: gltfResource, - }); - }).toThrowDeveloperError(); - }); - - it("loadBufferView throws if baseResource is undefined", function () { - expect(function () { - ResourceCache.loadBufferView({ - gltf: gltfUncompressed, - bufferViewId: 0, - gltfResource: gltfResource, - baseResource: undefined, - }); - }).toThrowDeveloperError(); - }); + expect(cacheEntry.referenceCount).toBe(2); + }); - it("loads draco", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(dracoArrayBuffer) - ); - - spyOn(DracoLoader, "decodeBufferView").and.returnValue( - Promise.resolve(decodeDracoResults) - ); - - const expectedCacheKey = ResourceCacheKey.getDracoCacheKey({ - gltf: gltfDraco, + it("getDracoLoader throws if gltf is undefined", function () { + expect(() => + ResourceCache.getDracoLoader({ + gltf: undefined, draco: dracoExtension, gltfResource: gltfResource, baseResource: gltfResource, - }); - const dracoLoader = ResourceCache.loadDraco({ + }) + ).toThrowDeveloperError(); + }); + + it("getDracoLoader throws if draco is undefined", function () { + expect(() => + ResourceCache.getDracoLoader({ gltf: gltfDraco, - draco: dracoExtension, + draco: undefined, gltfResource: gltfResource, baseResource: gltfResource, - }); - - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(dracoLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadDraco({ - gltf: gltfDraco, - draco: dracoExtension, - gltfResource: gltfResource, - baseResource: gltfResource, - }) - ).toBe(dracoLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return waitForLoaderProcess(dracoLoader, scene).then(function ( - dracoLoader - ) { - expect(dracoLoader.decodedData).toBeDefined(); - }); - }); - - it("loadDraco throws if gltf is undefined", function () { - expect(function () { - ResourceCache.loadBufferView({ - gltf: undefined, - draco: dracoExtension, - gltfResource: gltfResource, - baseResource: gltfResource, - }); - }).toThrowDeveloperError(); - }); - - it("loadDraco throws if draco is undefined", function () { - expect(function () { - ResourceCache.loadBufferView({ - gltf: gltfDraco, - draco: undefined, - gltfResource: gltfResource, - baseResource: gltfResource, - }); - }).toThrowDeveloperError(); - }); + }) + ).toThrowDeveloperError(); + }); - it("loadDraco throws if gltfResource is undefined", function () { - expect(function () { - ResourceCache.loadBufferView({ - gltf: gltfDraco, - draco: dracoExtension, - gltfResource: undefined, - baseResource: gltfResource, - }); - }).toThrowDeveloperError(); - }); - - it("loadDraco throws if baseResource is undefined", function () { - expect(function () { - ResourceCache.loadBufferView({ - gltf: gltfDraco, - draco: dracoExtension, - gltfResource: gltfResource, - baseResource: undefined, - }); - }).toThrowDeveloperError(); - }); - - it("loads vertex buffer from buffer view", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(bufferArrayBuffer) - ); + it("getDracoLoader throws if gltfResource is undefined", function () { + expect(() => + ResourceCache.getDracoLoader({ + gltf: gltfDraco, + draco: dracoExtension, + gltfResource: undefined, + baseResource: gltfResource, + }) + ).toThrowDeveloperError(); + }); - const expectedCacheKey = ResourceCacheKey.getVertexBufferCacheKey({ - gltf: gltfUncompressed, + it("getDracoLoader throws if baseResource is undefined", function () { + expect(() => + ResourceCache.getDracoLoader({ + gltf: gltfDraco, + draco: dracoExtension, gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - bufferViewId: 0, - loadBuffer: true, - }); - const vertexBufferLoader = ResourceCache.loadVertexBuffer({ + baseResource: undefined, + }) + ).toThrowDeveloperError(); + }); + + it("gets vertex buffer loader for buffer view", function () { + const expectedCacheKey = ResourceCacheKey.getVertexBufferCacheKey({ + gltf: gltfUncompressed, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + bufferViewId: 0, + loadBuffer: true, + }); + const vertexBufferLoader = ResourceCache.getVertexBufferLoader({ + gltf: gltfUncompressed, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + bufferViewId: 0, + accessorId: 0, + loadBuffer: true, + }); + + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(vertexBufferLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(vertexBufferLoader).toBeInstanceOf(GltfVertexBufferLoader); + + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getVertexBufferLoader({ gltf: gltfUncompressed, gltfResource: gltfResource, baseResource: gltfResource, frameState: mockFrameState, bufferViewId: 0, - accessorId: 0, loadBuffer: true, - }); - - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(vertexBufferLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadVertexBuffer({ - gltf: gltfUncompressed, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - bufferViewId: 0, - loadBuffer: true, - }) - ).toBe(vertexBufferLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - expect(vertexBufferLoader.buffer).toBeDefined(); - - return cacheEntry._statisticsPromise.then(function () { - // The vertex buffer should only be counted once - expect(ResourceCache.statistics.geometryByteLength).toBe( - vertexBufferLoader.buffer.sizeInBytes - ); - }); - }); - }); - - it("loads vertex buffer from draco", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(dracoArrayBuffer) - ); - - spyOn(DracoLoader, "decodeBufferView").and.returnValue( - Promise.resolve(decodeDracoResults) - ); - - const expectedCacheKey = ResourceCacheKey.getVertexBufferCacheKey({ + }) + ).toBe(vertexBufferLoader); + + expect(cacheEntry.referenceCount).toBe(2); + }); + + it("gets vertex buffer loader for draco", function () { + const expectedCacheKey = ResourceCacheKey.getVertexBufferCacheKey({ + gltf: gltfDraco, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + draco: dracoExtension, + attributeSemantic: "POSITION", + loadBuffer: true, + }); + const vertexBufferLoader = ResourceCache.getVertexBufferLoader({ + gltf: gltfDraco, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + draco: dracoExtension, + attributeSemantic: "POSITION", + accessorId: 0, + loadBuffer: true, + }); + + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(vertexBufferLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(vertexBufferLoader).toBeInstanceOf(GltfVertexBufferLoader); + + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getVertexBufferLoader({ gltf: gltfDraco, gltfResource: gltfResource, baseResource: gltfResource, frameState: mockFrameState, draco: dracoExtension, attributeSemantic: "POSITION", + accessorId: 0, loadBuffer: true, - }); - const vertexBufferLoader = ResourceCache.loadVertexBuffer({ - gltf: gltfDraco, + }) + ).toBe(vertexBufferLoader); + + expect(cacheEntry.referenceCount).toBe(2); + }); + + it("gets vertex buffer loaders on different contexts", function () { + const vertexBufferLoader1 = ResourceCache.getVertexBufferLoader({ + gltf: gltfUncompressed, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + bufferViewId: 0, + accessorId: 0, + loadBuffer: true, + }); + + const vertexBufferLoader2 = ResourceCache.getVertexBufferLoader({ + gltf: gltfUncompressed, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState2, + bufferViewId: 0, + accessorId: 0, + loadBuffer: true, + }); + + expect(vertexBufferLoader1).toBeDefined(); + expect(vertexBufferLoader2).toBeDefined(); + + expect(vertexBufferLoader1).not.toBe(vertexBufferLoader2); + }); + + it("getVertexBufferLoader throws if gltf is undefined", function () { + expect(() => + ResourceCache.getVertexBufferLoader({ + gltf: undefined, gltfResource: gltfResource, baseResource: gltfResource, frameState: mockFrameState, - draco: dracoExtension, - attributeSemantic: "POSITION", - accessorId: 0, + bufferViewId: 0, loadBuffer: true, - }); - - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(vertexBufferLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadVertexBuffer({ - gltf: gltfDraco, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - draco: dracoExtension, - attributeSemantic: "POSITION", - accessorId: 0, - loadBuffer: true, - }) - ).toBe(vertexBufferLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - expect(vertexBufferLoader.buffer).toBeDefined(); - - return cacheEntry._statisticsPromise.then(function () { - // The vertex buffer should only be counted once - expect(ResourceCache.statistics.geometryByteLength).toBe( - vertexBufferLoader.buffer.sizeInBytes - ); - }); - }); - }); - - it("loads vertex buffer as typed array", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(bufferArrayBuffer) - ); + }) + ).toThrowDeveloperError(); + }); - const expectedCacheKey = ResourceCacheKey.getVertexBufferCacheKey({ + it("getVertexBufferLoader throws if gltfResource is undefined", function () { + expect(() => + ResourceCache.getVertexBufferLoader({ gltf: gltfUncompressed, - gltfResource: gltfResource, + gltfResource: undefined, baseResource: gltfResource, frameState: mockFrameState, bufferViewId: 0, - loadTypedArray: true, - }); - const vertexBufferLoader = ResourceCache.loadVertexBuffer({ + loadBuffer: true, + }) + ).toThrowDeveloperError(); + }); + + it("getVertexBufferLoader throws if baseResource is undefined", function () { + expect(() => + ResourceCache.getVertexBufferLoader({ gltf: gltfUncompressed, gltfResource: gltfResource, - baseResource: gltfResource, + baseResource: undefined, frameState: mockFrameState, bufferViewId: 0, - accessorId: 0, - loadTypedArray: true, - }); - - expect(vertexBufferLoader.cacheKey).toBe(expectedCacheKey); - - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - expect(vertexBufferLoader.typedArray).toBeDefined(); - expect(vertexBufferLoader.buffer).toBeUndefined(); - - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - return cacheEntry._statisticsPromise.then(function () { - expect(ResourceCache.statistics.geometryByteLength).toBe( - vertexBufferLoader.typedArray.byteLength - ); - }); - }); - }); - - it("loads vertex buffer as buffer and typed array for 2D", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(bufferArrayBuffer) - ); + loadBuffer: true, + }) + ).toThrowDeveloperError(); + }); - const expectedCacheKey = ResourceCacheKey.getVertexBufferCacheKey({ + it("getVertexBufferLoader throws if frameState is undefined", function () { + expect(() => + ResourceCache.getVertexBufferLoader({ gltf: gltfUncompressed, gltfResource: gltfResource, baseResource: gltfResource, - frameState: mockFrameState, + frameState: undefined, bufferViewId: 0, loadBuffer: true, - loadTypedArray: true, - }); - const vertexBufferLoader = ResourceCache.loadVertexBuffer({ - gltf: gltfUncompressed, + }) + ).toThrowDeveloperError(); + }); + + it("getVertexBufferLoader throws if bufferViewId and draco are both defined", function () { + expect(() => + ResourceCache.getVertexBufferLoader({ + gltf: gltfDraco, gltfResource: gltfResource, baseResource: gltfResource, frameState: mockFrameState, bufferViewId: 0, + draco: dracoExtension, + attributeSemantic: "POSITION", accessorId: 0, loadBuffer: true, - loadTypedArray: true, - }); - - expect(vertexBufferLoader.cacheKey).toBe(expectedCacheKey); - - return waitForLoaderProcess(vertexBufferLoader, scene).then(function ( - vertexBufferLoader - ) { - expect(vertexBufferLoader.typedArray).toBeDefined(); - expect(vertexBufferLoader.buffer).toBeDefined(); - - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - return cacheEntry._statisticsPromise.then(function () { - const totalSize = - vertexBufferLoader.typedArray.byteLength + - vertexBufferLoader.buffer.sizeInBytes; - expect(ResourceCache.statistics.geometryByteLength).toBe(totalSize); - }); - }); - }); + }) + ).toThrowDeveloperError(); + }); - it("loads vertex buffer on different contexts", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(bufferArrayBuffer) - ); - - const vertexBufferLoader1 = ResourceCache.loadVertexBuffer({ - gltf: gltfUncompressed, + it("getVertexBufferLoader throws if bufferViewId and draco are both undefined", function () { + expect(() => + ResourceCache.getVertexBufferLoader({ + gltf: gltfDraco, gltfResource: gltfResource, baseResource: gltfResource, frameState: mockFrameState, - bufferViewId: 0, - accessorId: 0, loadBuffer: true, - }); + }) + ).toThrowDeveloperError(); + }); - const vertexBufferLoader2 = ResourceCache.loadVertexBuffer({ - gltf: gltfUncompressed, + it("getVertexBufferLoader throws if draco is defined and attributeSemantic is not defined", function () { + expect(() => + ResourceCache.getVertexBufferLoader({ + gltf: gltfDraco, gltfResource: gltfResource, baseResource: gltfResource, - frameState: mockFrameState2, - bufferViewId: 0, + frameState: mockFrameState, + draco: dracoExtension, + attributeSemantic: undefined, accessorId: 0, loadBuffer: true, - }); - - const promises = [ - waitForLoaderProcess(vertexBufferLoader1, scene), - waitForLoaderProcess(vertexBufferLoader2, scene), - ]; - - return Promise.all(promises).then(function (vertexBufferLoaders) { - const vertexBuffer1 = vertexBufferLoaders[0]; - const vertexBuffer2 = vertexBufferLoaders[1]; - - expect(vertexBuffer1).toBeDefined(); - expect(vertexBuffer2).toBeDefined(); - - expect(vertexBuffer1).not.toBe(vertexBuffer2); - }); - }); - - it("loadVertexBuffer throws if gltf is undefined", function () { - expect(function () { - ResourceCache.loadVertexBuffer({ - gltf: undefined, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - bufferViewId: 0, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); - - it("loadVertexBuffer throws if gltfResource is undefined", function () { - expect(function () { - ResourceCache.loadVertexBuffer({ - gltf: gltfUncompressed, - gltfResource: undefined, - baseResource: gltfResource, - frameState: mockFrameState, - bufferViewId: 0, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); - - it("loadVertexBuffer throws if baseResource is undefined", function () { - expect(function () { - ResourceCache.loadVertexBuffer({ - gltf: gltfUncompressed, - gltfResource: gltfResource, - baseResource: undefined, - frameState: mockFrameState, - bufferViewId: 0, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); + }) + ).toThrowDeveloperError(); + }); - it("loadVertexBuffer throws if frameState is undefined", function () { - expect(function () { - ResourceCache.loadVertexBuffer({ - gltf: gltfUncompressed, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: undefined, - bufferViewId: 0, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); - - it("loadVertexBuffer throws if bufferViewId and draco are both defined", function () { - expect(function () { - ResourceCache.loadVertexBuffer({ - gltf: gltfDraco, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - bufferViewId: 0, - draco: dracoExtension, - attributeSemantic: "POSITION", - accessorId: 0, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); - - it("loadVertexBuffer throws if bufferViewId and draco are both undefined", function () { - expect(function () { - ResourceCache.loadVertexBuffer({ - gltf: gltfDraco, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); - - it("loadVertexBuffer throws if draco is defined and attributeSemantic is not defined", function () { - expect(function () { - ResourceCache.loadVertexBuffer({ - gltf: gltfDraco, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - draco: dracoExtension, - attributeSemantic: undefined, - accessorId: 0, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); - - it("loadVertexBuffer throws if draco is defined and accessorId is not defined", function () { - expect(function () { - ResourceCache.loadVertexBuffer({ - gltf: gltfDraco, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - draco: dracoExtension, - attributeSemantic: "POSITION", - accessorId: undefined, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); - - it("loadVertexBuffer throws if both loadBuffer and loadTypedArray are false", function () { - expect(function () { - ResourceCache.loadVertexBuffer({ - gltf: gltfUncompressed, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - bufferViewId: 0, - loadBuffer: false, - loadTypedArray: false, - }); - }).toThrowDeveloperError(); - }); - - it("loads index buffer from accessor as buffer", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(bufferArrayBuffer) - ); - - const expectedCacheKey = ResourceCacheKey.getIndexBufferCacheKey({ - gltf: gltfUncompressed, - accessorId: 2, + it("getVertexBufferLoader throws if draco is defined and accessorId is not defined", function () { + expect(() => + ResourceCache.getVertexBufferLoader({ + gltf: gltfDraco, gltfResource: gltfResource, baseResource: gltfResource, frameState: mockFrameState, + draco: dracoExtension, + attributeSemantic: "POSITION", + accessorId: undefined, loadBuffer: true, - }); - const indexBufferLoader = ResourceCache.loadIndexBuffer({ + }) + ).toThrowDeveloperError(); + }); + + it("getVertexBufferLoader throws if both loadBuffer and loadTypedArray are false", function () { + expect(() => + ResourceCache.getVertexBufferLoader({ gltf: gltfUncompressed, - accessorId: 2, gltfResource: gltfResource, baseResource: gltfResource, frameState: mockFrameState, - loadBuffer: true, - }); - - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(indexBufferLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadIndexBuffer({ - gltf: gltfUncompressed, - accessorId: 2, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - loadBuffer: true, - }) - ).toBe(indexBufferLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.buffer).toBeDefined(); - expect(indexBufferLoader.typedArray).toBeUndefined(); - - expect(ResourceCache.statistics.geometryByteLength).toBe( - indexBufferLoader.buffer.sizeInBytes - ); - }); - }); - - it("loads index buffer from draco", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(dracoArrayBuffer) - ); - - spyOn(DracoLoader, "decodeBufferView").and.returnValue( - Promise.resolve(decodeDracoResults) - ); - - const expectedCacheKey = ResourceCacheKey.getIndexBufferCacheKey({ - gltf: gltfDraco, + bufferViewId: 0, + loadBuffer: false, + loadTypedArray: false, + }) + ).toThrowDeveloperError(); + }); + + it("gets index buffer loader for accessor as buffer", function () { + const expectedCacheKey = ResourceCacheKey.getIndexBufferCacheKey({ + gltf: gltfUncompressed, + accessorId: 2, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + loadBuffer: true, + }); + const indexBufferLoader = ResourceCache.getIndexBufferLoader({ + gltf: gltfUncompressed, + accessorId: 2, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + loadBuffer: true, + }); + + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(indexBufferLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(indexBufferLoader).toBeInstanceOf(GltfIndexBufferLoader); + + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getIndexBufferLoader({ + gltf: gltfUncompressed, accessorId: 2, gltfResource: gltfResource, baseResource: gltfResource, frameState: mockFrameState, - draco: dracoExtension, loadBuffer: true, - }); - const indexBufferLoader = ResourceCache.loadIndexBuffer({ + }) + ).toBe(indexBufferLoader); + + expect(cacheEntry.referenceCount).toBe(2); + }); + + it("loads index buffer from draco", function () { + const expectedCacheKey = ResourceCacheKey.getIndexBufferCacheKey({ + gltf: gltfDraco, + accessorId: 2, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + draco: dracoExtension, + loadBuffer: true, + }); + const indexBufferLoader = ResourceCache.getIndexBufferLoader({ + gltf: gltfDraco, + accessorId: 2, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + draco: dracoExtension, + loadBuffer: true, + }); + + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(indexBufferLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(indexBufferLoader).toBeInstanceOf(GltfIndexBufferLoader); + + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getIndexBufferLoader({ gltf: gltfDraco, accessorId: 2, gltfResource: gltfResource, @@ -1297,491 +962,328 @@ describe( frameState: mockFrameState, draco: dracoExtension, loadBuffer: true, - }); - - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(indexBufferLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadIndexBuffer({ - gltf: gltfDraco, - accessorId: 2, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - draco: dracoExtension, - loadBuffer: true, - }) - ).toBe(indexBufferLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.buffer).toBeDefined(); - - return cacheEntry._statisticsPromise.then(function () { - // The index buffer should only be counted once - expect(ResourceCache.statistics.geometryByteLength).toBe( - indexBufferLoader.buffer.sizeInBytes - ); - }); - }); + }) + ).toBe(indexBufferLoader); + + expect(cacheEntry.referenceCount).toBe(2); + }); + + it("gets index buffer loaders on different contexts", function () { + const indexBufferLoader1 = ResourceCache.getIndexBufferLoader({ + gltf: gltfUncompressed, + accessorId: 2, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + loadBuffer: true, }); - it("loads index buffer as typed array", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(bufferArrayBuffer) - ); + const indexBufferLoader2 = ResourceCache.getIndexBufferLoader({ + gltf: gltfUncompressed, + accessorId: 2, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState2, + loadBuffer: true, + }); - const expectedCacheKey = ResourceCacheKey.getIndexBufferCacheKey({ - gltf: gltfUncompressed, + expect(indexBufferLoader1).toBeDefined(); + expect(indexBufferLoader2).toBeDefined(); + + expect(indexBufferLoader1).not.toBe(indexBufferLoader2); + }); + + it("getIndexBufferLoader throws if gltf is undefined", function () { + expect(() => + ResourceCache.getIndexBufferLoader({ + gltf: undefined, accessorId: 2, gltfResource: gltfResource, baseResource: gltfResource, frameState: mockFrameState, - loadTypedArray: true, - }); - const indexBufferLoader = ResourceCache.loadIndexBuffer({ + loadBuffer: true, + }) + ).toThrowDeveloperError(); + }); + + it("getIndexBufferLoader throws if accessorId is undefined", function () { + expect(() => + ResourceCache.getIndexBufferLoader({ gltf: gltfUncompressed, - accessorId: 2, + accessorId: undefined, gltfResource: gltfResource, baseResource: gltfResource, frameState: mockFrameState, - loadTypedArray: true, - }); - - expect(indexBufferLoader.cacheKey).toBe(expectedCacheKey); - - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.typedArray).toBeDefined(); - expect(indexBufferLoader.buffer).toBeUndefined(); - - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - return cacheEntry._statisticsPromise.then(function () { - expect(ResourceCache.statistics.geometryByteLength).toBe( - indexBufferLoader.typedArray.byteLength - ); - }); - }); - }); - - it("loads index buffer as buffer and typed array", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(bufferArrayBuffer) - ); + loadBuffer: true, + }) + ).toThrowDeveloperError(); + }); - const expectedCacheKey = ResourceCacheKey.getIndexBufferCacheKey({ + it("getIndexBufferLoader throws if gltfResource is undefined", function () { + expect(() => + ResourceCache.getIndexBufferLoader({ gltf: gltfUncompressed, accessorId: 2, - gltfResource: gltfResource, + gltfResource: undefined, baseResource: gltfResource, frameState: mockFrameState, loadBuffer: true, - loadTypedArray: true, - }); - const indexBufferLoader = ResourceCache.loadIndexBuffer({ + }) + ).toThrowDeveloperError(); + }); + + it("getIndexBufferLoader throws if baseResource is undefined", function () { + expect(() => + ResourceCache.getIndexBufferLoader({ gltf: gltfUncompressed, accessorId: 2, gltfResource: gltfResource, - baseResource: gltfResource, + baseResource: undefined, frameState: mockFrameState, loadBuffer: true, - loadTypedArray: true, - }); - - expect(indexBufferLoader.cacheKey).toBe(expectedCacheKey); - - return waitForLoaderProcess(indexBufferLoader, scene).then(function ( - indexBufferLoader - ) { - expect(indexBufferLoader.typedArray).toBeDefined(); - expect(indexBufferLoader.buffer).toBeDefined(); - - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - return cacheEntry._statisticsPromise.then(function () { - // The statistics will count both buffer and typed array - expect(ResourceCache.statistics.geometryByteLength).toBe( - 2 * indexBufferLoader.typedArray.byteLength - ); - }); - }); - }); - - it("loads index buffer on different contexts", function () { - spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( - Promise.resolve(bufferArrayBuffer) - ); + }) + ).toThrowDeveloperError(); + }); - const indexBufferLoader1 = ResourceCache.loadIndexBuffer({ + it("getIndexBufferLoader throws if frameState is undefined", function () { + expect(() => + ResourceCache.getIndexBufferLoader({ gltf: gltfUncompressed, accessorId: 2, gltfResource: gltfResource, baseResource: gltfResource, - frameState: mockFrameState, + frameState: undefined, loadBuffer: true, - }); + }) + ).toThrowDeveloperError(); + }); - const indexBufferLoader2 = ResourceCache.loadIndexBuffer({ + it("getIndexBufferLoader throws if both loadBuffer and loadTypedArray are false", function () { + expect(() => + ResourceCache.getIndexBufferLoader({ gltf: gltfUncompressed, accessorId: 2, gltfResource: gltfResource, baseResource: gltfResource, - frameState: mockFrameState2, - loadBuffer: true, - }); - - const promises = [ - waitForLoaderProcess(indexBufferLoader1, scene), - waitForLoaderProcess(indexBufferLoader2, scene), - ]; - - return Promise.all(promises).then(function (indexBufferLoaders) { - const indexBuffer1 = indexBufferLoaders[0]; - const indexBuffer2 = indexBufferLoaders[1]; - - expect(indexBuffer1).toBeDefined(); - expect(indexBuffer2).toBeDefined(); - - expect(indexBuffer1).not.toBe(indexBuffer2); - }); - }); - - it("loadIndexBuffer throws if gltf is undefined", function () { - expect(function () { - ResourceCache.loadIndexBuffer({ - gltf: undefined, - accessorId: 2, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); - - it("loadIndexBuffer throws if accessorId is undefined", function () { - expect(function () { - ResourceCache.loadIndexBuffer({ - gltf: gltfUncompressed, - accessorId: undefined, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); - - it("loadIndexBuffer throws if gltfResource is undefined", function () { - expect(function () { - ResourceCache.loadIndexBuffer({ - gltf: gltfUncompressed, - accessorId: 2, - gltfResource: undefined, - baseResource: gltfResource, - frameState: mockFrameState, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); - - it("loadIndexBuffer throws if baseResource is undefined", function () { - expect(function () { - ResourceCache.loadIndexBuffer({ - gltf: gltfUncompressed, - accessorId: 2, - gltfResource: gltfResource, - baseResource: undefined, - frameState: mockFrameState, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); + frameState: mockFrameState, + loadBuffer: false, + loadTypedArray: false, + }) + ).toThrowDeveloperError(); + }); + + it("gets image loader", function () { + const expectedCacheKey = ResourceCacheKey.getImageCacheKey({ + gltf: gltfWithTextures, + imageId: 0, + gltfResource: gltfResource, + baseResource: gltfResource, + }); + const imageLoader = ResourceCache.getImageLoader({ + gltf: gltfWithTextures, + imageId: 0, + gltfResource: gltfResource, + baseResource: gltfResource, + }); + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(imageLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(imageLoader).toBeInstanceOf(GltfImageLoader); + + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getImageLoader({ + gltf: gltfWithTextures, + imageId: 0, + gltfResource: gltfResource, + baseResource: gltfResource, + }) + ).toBe(imageLoader); - it("loadIndexBuffer throws if frameState is undefined", function () { - expect(function () { - ResourceCache.loadIndexBuffer({ - gltf: gltfUncompressed, - accessorId: 2, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: undefined, - loadBuffer: true, - }); - }).toThrowDeveloperError(); - }); + expect(cacheEntry.referenceCount).toBe(2); + }); - it("loadIndexBuffer throws if both loadBuffer and loadTypedArray are false", function () { - expect(function () { - ResourceCache.loadIndexBuffer({ - gltf: gltfUncompressed, - accessorId: 2, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - loadBuffer: false, - loadTypedArray: false, - }); - }).toThrowDeveloperError(); - }); + it("getImageLoader throws if gltf is undefined", function () { + expect(() => + ResourceCache.getImageLoader({ + gltf: undefined, + imageId: 0, + gltfResource: gltfResource, + baseResource: gltfResource, + }) + ).toThrowDeveloperError(); + }); - it("loads image", function () { - spyOn(Resource.prototype, "fetchImage").and.returnValue( - Promise.resolve(image) - ); + it("getImageLoader throws if imageId is undefined", function () { + expect(() => + ResourceCache.getImageLoader({ + gltf: gltfWithTextures, + imageId: undefined, + gltfResource: gltfResource, + baseResource: gltfResource, + }) + ).toThrowDeveloperError(); + }); - const expectedCacheKey = ResourceCacheKey.getImageCacheKey({ + it("getImageLoader throws if gltfResource is undefined", function () { + expect(() => + ResourceCache.getImageLoader({ gltf: gltfWithTextures, imageId: 0, - gltfResource: gltfResource, + gltfResource: undefined, baseResource: gltfResource, - }); - const imageLoader = ResourceCache.loadImage({ + }) + ).toThrowDeveloperError(); + }); + + it("getImageLoader throws if baseResource is undefined", function () { + expect(() => + ResourceCache.getImageLoader({ gltf: gltfWithTextures, imageId: 0, gltfResource: gltfResource, + baseResource: undefined, + }) + ).toThrowDeveloperError(); + }); + + it("gets texture loader", function () { + const expectedCacheKey = ResourceCacheKey.getTextureCacheKey({ + gltf: gltfWithTextures, + textureInfo: gltfWithTextures.materials[0].emissiveTexture, + gltfResource: gltfResource, + baseResource: gltfResource, + supportedImageFormats: new SupportedImageFormats(), + frameState: mockFrameState, + }); + const textureLoader = ResourceCache.getTextureLoader({ + gltf: gltfWithTextures, + textureInfo: gltfWithTextures.materials[0].emissiveTexture, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + supportedImageFormats: new SupportedImageFormats(), + }); + const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; + expect(textureLoader.cacheKey).toBe(expectedCacheKey); + expect(cacheEntry.referenceCount).toBe(1); + expect(textureLoader).toBeInstanceOf(GltfTextureLoader); + + // The existing resource is returned if the computed cache key is the same + expect( + ResourceCache.getTextureLoader({ + gltf: gltfWithTextures, + textureInfo: gltfWithTextures.materials[0].emissiveTexture, + gltfResource: gltfResource, baseResource: gltfResource, - }); - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(imageLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadImage({ - gltf: gltfWithTextures, - imageId: 0, - gltfResource: gltfResource, - baseResource: gltfResource, - }) - ).toBe(imageLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return imageLoader.promise.then(function (imageLoader) { - expect(imageLoader.image).toBeDefined(); - }); - }); + frameState: mockFrameState, + supportedImageFormats: new SupportedImageFormats(), + }) + ).toBe(textureLoader); - it("loadImage throws if gltf is undefined", function () { - expect(function () { - ResourceCache.loadImage({ - gltf: undefined, - imageId: 0, - gltfResource: gltfResource, - baseResource: gltfResource, - }); - }).toThrowDeveloperError(); - }); + expect(cacheEntry.referenceCount).toBe(2); + }); - it("loadImage throws if imageId is undefined", function () { - expect(function () { - ResourceCache.loadImage({ - gltf: gltfWithTextures, - imageId: undefined, - gltfResource: gltfResource, - baseResource: gltfResource, - }); - }).toThrowDeveloperError(); + it("get texture loaders in different contexts", function () { + const textureLoader1 = ResourceCache.getTextureLoader({ + gltf: gltfWithTextures, + textureInfo: gltfWithTextures.materials[0].emissiveTexture, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState, + supportedImageFormats: new SupportedImageFormats(), }); - it("loadImage throws if gltfResource is undefined", function () { - expect(function () { - ResourceCache.loadImage({ - gltf: gltfWithTextures, - imageId: 0, - gltfResource: undefined, - baseResource: gltfResource, - }); - }).toThrowDeveloperError(); + const textureLoader2 = ResourceCache.getTextureLoader({ + gltf: gltfWithTextures, + textureInfo: gltfWithTextures.materials[0].emissiveTexture, + gltfResource: gltfResource, + baseResource: gltfResource, + frameState: mockFrameState2, + supportedImageFormats: new SupportedImageFormats(), }); - it("loadImage throws if baseResource is undefined", function () { - expect(function () { - ResourceCache.loadImage({ - gltf: gltfWithTextures, - imageId: 0, - gltfResource: gltfResource, - baseResource: undefined, - }); - }).toThrowDeveloperError(); - }); + expect(textureLoader1).toBeDefined(); + expect(textureLoader2).toBeDefined(); - it("loads texture", function () { - spyOn(Resource.prototype, "fetchImage").and.returnValue( - Promise.resolve(image) - ); + expect(textureLoader1).not.toBe(textureLoader2); + }); - const expectedCacheKey = ResourceCacheKey.getTextureCacheKey({ - gltf: gltfWithTextures, + it("getTextureLoader throws if gltf is undefined", function () { + expect(() => + ResourceCache.getTextureLoader({ + gltf: undefined, textureInfo: gltfWithTextures.materials[0].emissiveTexture, gltfResource: gltfResource, baseResource: gltfResource, supportedImageFormats: new SupportedImageFormats(), frameState: mockFrameState, - }); - const textureLoader = ResourceCache.loadTexture({ + }) + ).toThrowDeveloperError(); + }); + + it("getTextureLoader throws if textureInfo is undefined", function () { + expect(() => + ResourceCache.getTextureLoader({ gltf: gltfWithTextures, - textureInfo: gltfWithTextures.materials[0].emissiveTexture, + textureInfo: undefined, gltfResource: gltfResource, baseResource: gltfResource, + supportedImageFormats: new SupportedImageFormats(), frameState: mockFrameState, + }) + ).toThrowDeveloperError(); + }); + + it("getTextureLoader throws if gltfResource is undefined", function () { + expect(() => + ResourceCache.getTextureLoader({ + gltf: gltfWithTextures, + textureInfo: gltfWithTextures.materials[0].emissiveTexture, + gltfResource: undefined, + baseResource: gltfResource, supportedImageFormats: new SupportedImageFormats(), - }); - const cacheEntry = ResourceCache.cacheEntries[expectedCacheKey]; - expect(textureLoader.cacheKey).toBe(expectedCacheKey); - expect(cacheEntry.referenceCount).toBe(1); - - // The existing resource is returned if the computed cache key is the same - expect( - ResourceCache.loadTexture({ - gltf: gltfWithTextures, - textureInfo: gltfWithTextures.materials[0].emissiveTexture, - gltfResource: gltfResource, - baseResource: gltfResource, - frameState: mockFrameState, - supportedImageFormats: new SupportedImageFormats(), - }) - ).toBe(textureLoader); - - expect(cacheEntry.referenceCount).toBe(2); - - return waitForLoaderProcess(textureLoader, scene).then(function ( - textureLoader - ) { - expect(textureLoader.texture).toBeDefined(); - - return cacheEntry._statisticsPromise.then(function () { - // The texture should only be counted once - expect(ResourceCache.statistics.texturesByteLength).toBe( - textureLoader.texture.sizeInBytes - ); - }); - }); - }); + frameState: mockFrameState, + }) + ).toThrowDeveloperError(); + }); - it("loads texture on different contexts", function () { - spyOn(Resource.prototype, "fetchImage").and.returnValue( - Promise.resolve(image) - ); + it("getTextureLoader throws if baseResource is undefined", function () { + expect(() => + ResourceCache.getTextureLoader({ + gltf: gltfWithTextures, + textureInfo: gltfWithTextures.materials[0].emissiveTexture, + gltfResource: gltfResource, + baseResource: undefined, + supportedImageFormats: new SupportedImageFormats(), + frameState: mockFrameState, + }) + ).toThrowDeveloperError(); + }); - const textureLoader1 = ResourceCache.loadTexture({ + it("getTextureLoader throws if supportedImageFormats is undefined", function () { + expect(() => + ResourceCache.getTextureLoader({ gltf: gltfWithTextures, textureInfo: gltfWithTextures.materials[0].emissiveTexture, gltfResource: gltfResource, baseResource: gltfResource, + supportedImageFormats: undefined, frameState: mockFrameState, - supportedImageFormats: new SupportedImageFormats(), - }); + }) + ).toThrowDeveloperError(); + }); - const textureLoader2 = ResourceCache.loadTexture({ + it("getTextureLoader throws if frameState is undefined", function () { + expect(() => + ResourceCache.getTextureLoader({ gltf: gltfWithTextures, textureInfo: gltfWithTextures.materials[0].emissiveTexture, gltfResource: gltfResource, baseResource: gltfResource, - frameState: mockFrameState2, supportedImageFormats: new SupportedImageFormats(), - }); - - const promises = [ - waitForLoaderProcess(textureLoader1, scene), - waitForLoaderProcess(textureLoader2, scene), - ]; - - return Promise.all(promises).then(function (textureLoaders) { - const texture1 = textureLoaders[0]; - const texture2 = textureLoaders[1]; - - expect(texture1).toBeDefined(); - expect(texture2).toBeDefined(); - - expect(texture1).not.toBe(texture2); - }); - }); - - it("loadTexture throws if gltf is undefined", function () { - expect(function () { - ResourceCache.loadTexture({ - gltf: undefined, - textureInfo: gltfWithTextures.materials[0].emissiveTexture, - gltfResource: gltfResource, - baseResource: gltfResource, - supportedImageFormats: new SupportedImageFormats(), - frameState: mockFrameState, - }); - }).toThrowDeveloperError(); - }); - - it("loadTexture throws if textureInfo is undefined", function () { - expect(function () { - ResourceCache.loadTexture({ - gltf: gltfWithTextures, - textureInfo: undefined, - gltfResource: gltfResource, - baseResource: gltfResource, - supportedImageFormats: new SupportedImageFormats(), - frameState: mockFrameState, - }); - }).toThrowDeveloperError(); - }); - - it("loadTexture throws if gltfResource is undefined", function () { - expect(function () { - ResourceCache.loadTexture({ - gltf: gltfWithTextures, - textureInfo: gltfWithTextures.materials[0].emissiveTexture, - gltfResource: undefined, - baseResource: gltfResource, - supportedImageFormats: new SupportedImageFormats(), - frameState: mockFrameState, - }); - }).toThrowDeveloperError(); - }); - - it("loadTexture throws if baseResource is undefined", function () { - expect(function () { - ResourceCache.loadTexture({ - gltf: gltfWithTextures, - textureInfo: gltfWithTextures.materials[0].emissiveTexture, - gltfResource: gltfResource, - baseResource: undefined, - supportedImageFormats: new SupportedImageFormats(), - frameState: mockFrameState, - }); - }).toThrowDeveloperError(); - }); - - it("loadTexture throws if supportedImageFormats is undefined", function () { - expect(function () { - ResourceCache.loadTexture({ - gltf: gltfWithTextures, - textureInfo: gltfWithTextures.materials[0].emissiveTexture, - gltfResource: gltfResource, - baseResource: gltfResource, - supportedImageFormats: undefined, - frameState: mockFrameState, - }); - }).toThrowDeveloperError(); - }); - - it("loadTexture throws if frameState is undefined", function () { - expect(function () { - ResourceCache.loadTexture({ - gltf: gltfWithTextures, - textureInfo: gltfWithTextures.materials[0].emissiveTexture, - gltfResource: gltfResource, - baseResource: gltfResource, - supportedImageFormats: new SupportedImageFormats(), - frameState: undefined, - }); - }).toThrowDeveloperError(); - }); - }, - "WebGL" -); + frameState: undefined, + }) + ).toThrowDeveloperError(); + }); +}); diff --git a/packages/engine/Specs/Scene/ShadowMapSpec.js b/packages/engine/Specs/Scene/ShadowMapSpec.js index cc1d93085a04..a31056b9c366 100644 --- a/packages/engine/Specs/Scene/ShadowMapSpec.js +++ b/packages/engine/Specs/Scene/ShadowMapSpec.js @@ -281,18 +281,17 @@ describe( ); } - function loadModel(options) { - const model = scene.primitives.add(Model.fromGltf(options)); - return pollToPromise( + async function loadModel(options) { + const model = scene.primitives.add(await Model.fromGltf(options)); + await pollToPromise( function () { // Render scene to progressively load the model scene.render(); return model.ready; }, { timeout: 10000 } - ).then(function () { - return model; - }); + ); + return model; } /** From d02e73908adf38f55365ec23fa673a7ceecf0e41 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Fri, 17 Mar 2023 14:20:46 -0400 Subject: [PATCH 02/18] Refactor loading promises --- Specs/Cesium3DTilesTester.js | 78 +- .../Source/DataSources/ModelVisualizer.js | 49 +- packages/engine/Source/Scene/Cesium3DTile.js | 315 ++-- .../Source/Scene/Cesium3DTileContent.js | 18 +- .../Scene/Cesium3DTileContentFactory.js | 11 +- .../Scene/Cesium3DTilesVoxelProvider.js | 43 +- .../engine/Source/Scene/Cesium3DTileset.js | 122 +- .../Source/Scene/Composite3DTileContent.js | 53 +- .../engine/Source/Scene/Empty3DTileContent.js | 4 +- .../Source/Scene/Geometry3DTileContent.js | 19 +- .../Source/Scene/GltfBufferViewLoader.js | 2 +- .../engine/Source/Scene/GltfImageLoader.js | 5 +- .../Source/Scene/GltfIndexBufferLoader.js | 5 +- .../engine/Source/Scene/GltfJsonLoader.js | 4 +- packages/engine/Source/Scene/GltfLoader.js | 77 +- .../engine/Source/Scene/GltfTextureLoader.js | 2 +- .../Source/Scene/GltfVertexBufferLoader.js | 5 +- .../Source/Scene/Implicit3DTileContent.js | 66 +- .../engine/Source/Scene/ImplicitSubtree.js | 109 +- .../engine/Source/Scene/Model/B3dmLoader.js | 88 +- .../Source/Scene/Model/GeoJsonLoader.js | 40 +- .../engine/Source/Scene/Model/I3dmLoader.js | 122 +- packages/engine/Source/Scene/Model/Model.js | 76 +- .../Source/Scene/Model/Model3DTileContent.js | 58 +- .../engine/Source/Scene/Model/PntsLoader.js | 59 +- .../Source/Scene/Multiple3DTileContent.js | 166 +- packages/engine/Source/Scene/ResourceCache.js | 10 +- .../Source/Scene/ResourceCacheStatistics.js | 3 +- .../Source/Scene/Tileset3DTileContent.js | 24 +- .../Scene/Vector3DTileClampedPolylines.js | 180 ++- .../Source/Scene/Vector3DTileContent.js | 34 +- .../Source/Scene/Vector3DTileGeometry.js | 106 +- .../engine/Source/Scene/Vector3DTilePoints.js | 136 +- .../Source/Scene/Vector3DTilePolygons.js | 228 +-- .../Source/Scene/Vector3DTilePolylines.js | 165 +- .../Specs/DataSources/ModelVisualizerSpec.js | 199 ++- .../engine/Specs/Scene/Cesium3DTilesetSpec.js | 763 +++++---- .../Specs/Scene/Composite3DTileContentSpec.js | 41 +- .../Specs/Scene/Geometry3DTileContentSpec.js | 28 +- .../Scene/GlobeSurfaceTileProviderSpec.js | 2 +- packages/engine/Specs/Scene/GltfLoaderSpec.js | 40 +- packages/engine/Specs/Scene/I3SNodeSpec.js | 6 +- .../Specs/Scene/Implicit3DTileContentSpec.js | 509 +++--- .../Specs/Scene/ImplicitMetadataViewSpec.js | 84 +- .../Specs/Scene/ImplicitSubtreeCacheSpec.js | 38 +- .../engine/Specs/Scene/ImplicitSubtreeSpec.js | 1424 ++++++++--------- .../Specs/Scene/Model/B3dmLoaderSpec.js | 24 +- .../Model/DequantizationPipelineStageSpec.js | 8 +- .../Scene/Model/FeatureIdPipelineStageSpec.js | 8 +- .../Specs/Scene/Model/GeoJsonLoaderSpec.js | 21 +- .../Scene/Model/GeometryPipelineStageSpec.js | 8 +- .../Specs/Scene/Model/I3dmLoaderSpec.js | 58 +- .../Model/InstancingPipelineStageSpec.js | 28 +- .../Scene/Model/MaterialPipelineStageSpec.js | 8 +- .../Scene/Model/MetadataPipelineStageSpec.js | 8 +- .../Scene/Model/Model3DTileContentSpec.js | 18 +- .../Model/ModelAnimationCollectionSpec.js | 2 +- .../Scene/Model/ModelMatrixUpdateStageSpec.js | 70 +- .../Specs/Scene/Model/ModelSceneGraphSpec.js | 2 - .../engine/Specs/Scene/Model/ModelSpec.js | 6 +- .../Model/MorphTargetsPipelineStageSpec.js | 8 +- .../Model/NodeStatisticsPipelineStageSpec.js | 8 +- .../Scene/Model/PickingPipelineStageSpec.js | 8 +- .../Specs/Scene/Model/PntsLoaderSpec.js | 70 +- .../PrimitiveOutlinePipelineStageSpec.js | 8 +- .../PrimitiveStatisticsPipelineStageSpec.js | 8 +- .../Model/SceneMode2DPipelineStageSpec.js | 8 +- .../SelectedFeatureIdPipelineStageSpec.js | 8 +- .../Scene/Model/SkinningPipelineStageSpec.js | 9 +- .../Scene/Model/WireframePipelineStageSpec.js | 8 +- .../Specs/Scene/Multiple3DTileContentSpec.js | 82 +- .../Scene/ResourceCacheStatisticsSpec.js | 312 ++-- .../engine/Specs/Scene/ResourceLoaderSpec.js | 3 - packages/engine/Specs/Scene/ShadowMapSpec.js | 2 +- .../Specs/Scene/Tileset3DTileContentSpec.js | 7 +- .../Specs/Scene/Vector3DTileContentSpec.js | 37 +- .../Specs/Scene/Vector3DTileGeometrySpec.js | 6 +- .../Specs/Scene/Vector3DTilePointsSpec.js | 6 +- .../Specs/Scene/Vector3DTilePolygonsSpec.js | 6 +- .../Specs/Scene/Vector3DTilePolylinesSpec.js | 6 +- 80 files changed, 3091 insertions(+), 3404 deletions(-) diff --git a/Specs/Cesium3DTilesTester.js b/Specs/Cesium3DTilesTester.js index 961829be8933..04318ceeadf7 100644 --- a/Specs/Cesium3DTilesTester.js +++ b/Specs/Cesium3DTilesTester.js @@ -1,16 +1,12 @@ import { - Cartesian2, Color, defaultValue, defined, JulianDate, - ImageBasedLighting, Resource, Cesium3DTileContentFactory, Cesium3DTileset, - PointCloudShading, TileBoundingSphere, - RuntimeError, } from "@cesium/engine"; import pollToPromise from "./pollToPromise.js"; @@ -129,87 +125,19 @@ Cesium3DTilesTester.loadTileset = function (scene, url, options) { }); }; -Cesium3DTilesTester.loadTileExpectError = function (scene, arrayBuffer, type) { - const tileset = {}; - const url = Resource.createIfNeeded(""); - expect(function () { - return Cesium3DTileContentFactory[type]( - tileset, - mockTile, - url, - arrayBuffer, - 0 - ); - }).toThrowError(RuntimeError); -}; - -Cesium3DTilesTester.loadTile = function (scene, arrayBuffer, type) { - const tileset = { - _statistics: { - batchTableByteLength: 0, - }, - root: {}, - }; - const url = Resource.createIfNeeded(""); - const content = Cesium3DTileContentFactory[type]( - tileset, - mockTile, - url, - arrayBuffer, - 0 - ); - content.update(tileset, scene.frameState); - return content; -}; - -// Use counter to prevent models from sharing the same cache key, -// this fixes tests that load a model with the same invalid url -let counter = 0; -Cesium3DTilesTester.rejectsReadyPromiseOnError = function ( - scene, +Cesium3DTilesTester.createContentForMockTile = async function ( arrayBuffer, type ) { - const tileset = { - basePath: counter++, - _statistics: { - batchTableByteLength: 0, - }, - imageBasedLighting: new ImageBasedLighting({ - imageBasedLighting: new Cartesian2(1, 1), - }), - pointCloudShading: new PointCloudShading(), - featureIdLabel: "featureId_0", - instanceFeatureIdLabel: "instanceFeatureId_0", - }; + const tileset = {}; const url = Resource.createIfNeeded(""); - const content = Cesium3DTileContentFactory[type]( + return Cesium3DTileContentFactory[type]( tileset, mockTile, url, arrayBuffer, 0 ); - content.update(tileset, scene.frameState); - - return content.readyPromise - .then(function (content) { - fail("should not resolve"); - }) - .catch(function (error) { - expect(error).toBeDefined(); - }); -}; - -Cesium3DTilesTester.resolvesReadyPromise = function (scene, url, options) { - return Cesium3DTilesTester.loadTileset(scene, url, options).then(function ( - tileset - ) { - const content = tileset.root.content; - return content.readyPromise.then(function (content) { - expect(content).toBeDefined(); - }); - }); }; Cesium3DTilesTester.tileDestroys = function (scene, url, options) { diff --git a/packages/engine/Source/DataSources/ModelVisualizer.js b/packages/engine/Source/DataSources/ModelVisualizer.js index 325ba027c67c..337e8012822b 100644 --- a/packages/engine/Source/DataSources/ModelVisualizer.js +++ b/packages/engine/Source/DataSources/ModelVisualizer.js @@ -36,6 +36,9 @@ const modelMatrixScratch = new Matrix4(); const nodeMatrixScratch = new Matrix4(); const scratchColor = new Color(); +const scratchArray = new Array(4); +const scratchCartesian = new Cartesian3(); + /** * A {@link Visualizer} which maps {@link Entity#model} to a {@link Model}. * @alias ModelVisualizer @@ -121,6 +124,7 @@ ModelVisualizer.prototype.update = function (time) { nodeTransformationsScratch: {}, articulationsScratch: {}, loadFailed: false, + modelUpdated: false, awaitingSampleTerrain: false, clampedBoundingSphere: undefined, sampleTerrainFailed: false, @@ -138,6 +142,11 @@ ModelVisualizer.prototype.update = function (time) { ), scene: this._scene, }); + + if (this.isDestroyed() || !defined(modelHash[entity.id])) { + return; + } + model.id = entity; primitives.add(model); modelHash[entity.id].modelPrimitive = model; @@ -146,20 +155,23 @@ ModelVisualizer.prototype.update = function (time) { return; } - console.error(error); + console.log(error); // Texture failures when incrementallyLoadTextures // will not affect the ability to compute the bounding sphere - if (error.name !== "TextureError") { + if ( + error.name !== "TextureError" && + model.incrementallyLoadTextures + ) { modelHash[entity.id].loadFailed = true; } }); } catch (error) { - if (!defined(modelHash[entity.id])) { + if (this.isDestroyed() || !defined(modelHash[entity.id])) { return; } - console.error(error); + console.log(error); modelHash[entity.id].loadFailed = true; } })(); @@ -241,15 +253,27 @@ ModelVisualizer.prototype.update = function (time) { time, defaultImageBasedLightingFactor ); - model.lightColor = Property.getValueOrUndefined( + let lightColor = Property.getValueOrUndefined( modelGraphics._lightColor, time ); + + // Convert from Color to Cartesian3 + if (defined(lightColor)) { + Color.pack(lightColor, scratchArray, 0); + lightColor = Cartesian3.unpack(scratchArray, 0, scratchCartesian); + } + + model.lightColor = lightColor; model.customShader = Property.getValueOrUndefined( modelGraphics._customShader, time ); + // It's possible for getBoundingSphere to run before + // model becomes ready and these properties are updated + modelHash[entity.id].modelUpdated = true; + if (model.ready) { const runAnimations = Property.getValueOrDefault( modelGraphics._runAnimations, @@ -393,7 +417,7 @@ ModelVisualizer.prototype.getBoundingSphere = function (entity, result) { const modelData = this._modelHash[entity.id]; if (!defined(modelData)) { - return BoundingSphereState.PENDING; + return BoundingSphereState.FAILED; } if (modelData.loadFailed) { @@ -402,12 +426,13 @@ ModelVisualizer.prototype.getBoundingSphere = function (entity, result) { const model = modelData.modelPrimitive; if (!defined(model) || !model.show) { - return BoundingSphereState.FAILED; + return BoundingSphereState.PENDING; } - if (!model.ready) { + if (!model.ready || !modelData.modelUpdated) { return BoundingSphereState.PENDING; } + const scene = this._scene; const globe = scene.globe; const ellipsoid = globe.ellipsoid; @@ -473,6 +498,10 @@ ModelVisualizer.prototype.getBoundingSphere = function (entity, result) { scratchCartographic, ]) .then((result) => { + if (this.isDestroyed()) { + return; + } + this._modelHash[entity.id].awaitingSampleTerrain = false; const updatedCartographic = result[0]; @@ -495,6 +524,10 @@ ModelVisualizer.prototype.getBoundingSphere = function (entity, result) { ); }) .catch((e) => { + if (this.isDestroyed()) { + return; + } + this._modelHash[entity.id].sampleTerrainFailed = true; this._modelHash[entity.id].awaitingSampleTerrain = false; }); diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js index 1fb9138aa66f..de95fdc92b4d 100644 --- a/packages/engine/Source/Scene/Cesium3DTile.js +++ b/packages/engine/Source/Scene/Cesium3DTile.js @@ -259,8 +259,6 @@ function Cesium3DTile(tileset, baseResource, header, parent) { this._content = content; this._contentResource = contentResource; this._contentState = contentState; - this._contentReadyToProcessPromise = undefined; - this._contentReadyPromise = undefined; this._expiredContent = undefined; this._serverKey = serverKey; @@ -710,42 +708,6 @@ Object.defineProperties(Cesium3DTile.prototype, { }, }, - /** - * Gets the promise that will be resolved when the tile's content is ready to process. - * This happens after the content is downloaded but before the content is ready - * to render. - *

- * The promise remains undefined until the tile's content is requested. - *

- * - * @type {Promise} - * @readonly - * - * @private - */ - contentReadyToProcessPromise: { - get: function () { - return this._contentReadyToProcessPromise; - }, - }, - - /** - * Gets the promise that will be resolved when the tile's content is ready to render. - *

- * The promise remains undefined until the tile's content is requested. - *

- * - * @type {Promise} - * @readonly - * - * @private - */ - contentReadyPromise: { - get: function () { - return this._contentReadyPromise; - }, - }, - /** * Returns the number of draw commands used by this tile. * @@ -1084,13 +1046,13 @@ function createPriorityFunction(tile) { * The request may not be made if the Cesium Request Scheduler can't prioritize it. *

* - * @return {number} The number of requests that were attempted but not scheduled. + * @return {Promise|undefined} A promise that resolves when the request completes, or undefined if there is no request needed, or the request cannot be scheduled. * @private */ Cesium3DTile.prototype.requestContent = function () { // empty contents don't require any HTTP requests if (this.hasEmptyContent) { - return 0; + return; } if (this.hasMultipleContents) { @@ -1112,7 +1074,7 @@ Cesium3DTile.prototype.requestContent = function () { * * @private * @param {Cesium3DTile} tile - * @returns {number} + * @returns {Promise|Promise|undefined} A promise that resolves to the tile content once loaded, or a promise that resolves to undefined if the request was cancelled mid-flight, or undefined if the request cannot be scheduled this frame */ function requestMultipleContents(tile) { let multipleContents = tile._content; @@ -1134,87 +1096,44 @@ function requestMultipleContents(tile) { tile._content = multipleContents; } - const backloggedRequestCount = multipleContents.requestInnerContents(); - if (backloggedRequestCount > 0) { - return backloggedRequestCount; + const promise = multipleContents.requestInnerContents(); + + if (!defined(promise)) { + // Request could not all be scheduled this frame + return; } tile._contentState = Cesium3DTileContentState.LOADING; - const contentReadyToProcessPromise = multipleContents.contentsFetchedPromise.then( - function () { - if ( - tile._contentState !== Cesium3DTileContentState.LOADING || - !defined(multipleContents.readyPromise) - ) { - // The tile or one of the inner content requests was canceled, - // short circuit. - return; - } - + return promise + .then(async (content) => { if (tile.isDestroyed()) { - multipleContentFailed(tile, tileset); + // Tile is unloaded before the content can process return; } - tile._contentState = Cesium3DTileContentState.PROCESSING; - return multipleContents; - } - ); - tile._contentReadyToProcessPromise = contentReadyToProcessPromise; - tile._contentReadyPromise = contentReadyToProcessPromise - .then(function (content) { + // Tile was canceled, try again later if (!defined(content)) { - // request was canceled, short circuit. return; } - return multipleContents.readyPromise; + tile._contentState = Cesium3DTileContentState.PROCESSING; + return multipleContents; }) - .then(function (content) { - if (!defined(content)) { - // tile was canceled, short circuit. - return; - } - + .catch((error) => { if (tile.isDestroyed()) { - multipleContentFailed(tile, tileset); + // Tile is unloaded before the content can process return; } - // Refresh style for expired content - tile._selectedFrame = 0; - tile.lastStyleTime = 0.0; - - JulianDate.now(tile._loadTimestamp); - tile._contentState = Cesium3DTileContentState.READY; - return content; - }) - .catch(function () { - multipleContentFailed(tile, tileset); + tile._contentState = Cesium3DTileContentState.FAILED; + throw error; }); - - return 0; -} - -/** - * @private - * @param {Cesium3DTile} tile - * @param {Cesium3DTileset} tileset - */ -function multipleContentFailed(tile, tileset) { - // note: The Multiple3DTileContent handles decrementing the number of pending - // requests if the state is LOADING. - if (tile._contentState === Cesium3DTileContentState.PROCESSING) { - --tileset.statistics.numberOfTilesProcessing; - } - - tile._contentState = Cesium3DTileContentState.FAILED; } /** * @private * @param {Cesium3DTile} tile - * @returns {number} + * @returns {Promise|Promise|undefined} A promise that resolves to the tile content once loaded; a promise that resolves to undefined if the tile was destroyed before processing can happen or the request was cancelled mid-flight; or undefined if the request cannot be scheduled this frame. */ function requestSingleContent(tile) { // it is important to clone here. The fetchArrayBuffer() below uses @@ -1238,90 +1157,81 @@ function requestSingleContent(tile) { tile._request = request; resource.request = request; - + const tileset = tile._tileset; const promise = resource.fetchArrayBuffer(); if (!defined(promise)) { - return 1; + ++tileset.statistics.numberOfAttemptedRequests; + return; } const previousState = tile._contentState; - const tileset = tile._tileset; tile._contentState = Cesium3DTileContentState.LOADING; ++tileset.statistics.numberOfPendingRequests; - const contentReadyToProcessPromise = promise.then(function (arrayBuffer) { + + return (async () => { + let arrayBuffer; + try { + arrayBuffer = await promise; + } catch (error) { + --tileset.statistics.numberOfPendingRequests; + if (tile.isDestroyed()) { + // Tile is unloaded before the content can process + return; + } + + if (request.cancelled || request.state === RequestState.CANCELLED) { + // Cancelled due to low priority - try again later. + tile._contentState = previousState; + ++tileset.statistics.numberOfAttemptedRequests; + return; + } + + tile._contentState = Cesium3DTileContentState.FAILED; + throw error; + } + if (tile.isDestroyed()) { - // Tile is unloaded before the content finishes loading - singleContentFailed(tile, tileset); + --tileset.statistics.numberOfPendingRequests; + // Tile is unloaded before the content can process return; } - const content = makeContent(tile, arrayBuffer); - - if (expired) { - tile.expireDate = undefined; + if (request.cancelled || request.state === RequestState.CANCELLED) { + // Cancelled due to low priority - try again later. + tile._contentState = previousState; + --tileset.statistics.numberOfPendingRequests; + ++tileset.statistics.numberOfAttemptedRequests; + return; } - tile._content = content; - tile._contentState = Cesium3DTileContentState.PROCESSING; - return content; - }); - tile._contentReadyToProcessPromise = contentReadyToProcessPromise; - tile._contentReadyPromise = contentReadyToProcessPromise - .then(function (content) { - if (!defined(content)) { - return; - } - + try { + const content = await makeContent(tile, arrayBuffer); --tileset.statistics.numberOfPendingRequests; - return content.readyPromise; - }) - .then(function (content) { - if (!defined(content)) { - return; - } if (tile.isDestroyed()) { - // Tile is unloaded before the content finishes processing - singleContentFailed(tile, tileset); + // Tile is unloaded before the content can process return; } - updateExpireDate(tile); - // Refresh style for expired content - tile._selectedFrame = 0; - tile.lastStyleTime = 0.0; + if (expired) { + tile.expireDate = undefined; + } + + tile._content = content; + tile._contentState = Cesium3DTileContentState.PROCESSING; - JulianDate.now(tile._loadTimestamp); - tile._contentState = Cesium3DTileContentState.READY; return content; - }) - .catch(function (error) { - if (request.state === RequestState.CANCELLED) { - // Cancelled due to low priority - try again later. - tile._contentState = previousState; - --tileset.statistics.numberOfPendingRequests; - ++tileset.statistics.numberOfAttemptedRequests; - return Promise.reject("Cancelled"); + } catch (error) { + --tileset.statistics.numberOfPendingRequests; + if (tile.isDestroyed()) { + // Tile is unloaded before the content can process + return; } - singleContentFailed(tile, tileset); - return Promise.reject(error); - }); - return 0; -} - -/** - * @private - * @param {Cesium3DTile} tile - * @param {Cesium3DTileset} tileset - */ -function singleContentFailed(tile, tileset) { - if (tile._contentState === Cesium3DTileContentState.PROCESSING) { - --tileset.statistics.numberOfTilesProcessing; - } else { - --tileset.statistics.numberOfPendingRequests; - } - tile._contentState = Cesium3DTileContentState.FAILED; + tile._contentState = Cesium3DTileContentState.FAILED; + throw error; + } + })(); } /** @@ -1332,10 +1242,10 @@ function singleContentFailed(tile, tileset) { * * @param {Cesium3DTile} tile The tile * @param {ArrayBuffer} arrayBuffer The downloaded payload containing data for the content - * @return {Cesium3DTileContent} A content object + * @return {Promise} A content object * @private */ -function makeContent(tile, arrayBuffer) { +async function makeContent(tile, arrayBuffer) { const preprocessed = preprocess3DTileContent(arrayBuffer); // Vector and Geometry tile rendering do not support the skip LOD optimization. @@ -1358,21 +1268,29 @@ function makeContent(tile, arrayBuffer) { let content; const contentFactory = Cesium3DTileContentFactory[preprocessed.contentType]; + if (tile.isDestroyed()) { + return; + } + if (defined(preprocessed.binaryPayload)) { - content = contentFactory( - tileset, - tile, - tile._contentResource, - preprocessed.binaryPayload.buffer, - 0 + content = await Promise.resolve( + contentFactory( + tileset, + tile, + tile._contentResource, + preprocessed.binaryPayload.buffer, + 0 + ) ); } else { // JSON formats - content = contentFactory( - tileset, - tile, - tile._contentResource, - preprocessed.jsonPayload + content = await Promise.resolve( + contentFactory( + tileset, + tile, + tile._contentResource, + preprocessed.jsonPayload + ) ); } @@ -1426,8 +1344,6 @@ Cesium3DTile.prototype.unloadContent = function () { this._content = this._content && this._content.destroy(); this._contentState = Cesium3DTileContentState.UNLOADED; - this._contentReadyToProcessPromise = undefined; - this._contentReadyPromise = undefined; this.lastStyleTime = 0.0; this.clippingPlanesDirty = this._clippingPlanesState === 0; @@ -1999,7 +1915,11 @@ function updateContent(tile, tileset, frameState) { if (!tile.hasMultipleContents && defined(expiredContent)) { if (!tile.contentReady) { // Render the expired content while the content loads - expiredContent.update(tileset, frameState); + try { + expiredContent.update(tileset, frameState); + } catch (error) { + // Eat error for expired content + } return; } @@ -2008,7 +1928,17 @@ function updateContent(tile, tileset, frameState) { tile._expiredContent = undefined; } - tile.content.update(tileset, frameState); + if (!defined(tile.content)) { + // Implicit placeholder tile + return; + } + + try { + tile.content.update(tileset, frameState); + } catch (error) { + tile._contentState = Cesium3DTileContentState.FAILED; + throw error; + } } /** @@ -2073,10 +2003,37 @@ const scratchCommandList = []; * @private */ Cesium3DTile.prototype.process = function (tileset, frameState) { + if (!this.contentExpired && !this.contentReady && this._content.ready) { + updateExpireDate(this); + + // Refresh style for expired content + this._selectedFrame = 0; + this.lastStyleTime = 0.0; + + JulianDate.now(this._loadTimestamp); + this._contentState = Cesium3DTileContentState.READY; + + if (!this.hasTilesetContent && !this.hasImplicitContent) { + // RESEARCH_IDEA: ability to unload tiles (without content) for an + // external tileset when all the tiles are unloaded. + tileset._statistics.incrementLoadCounts(this.content); + ++tileset._statistics.numberOfTilesWithContentReady; + ++tileset._statistics.numberOfLoadedTilesTotal; + + // Add to the tile cache. Previously expired tiles are already in the cache and won't get re-added. + tileset._cache.add(this); + } + } + const savedCommandList = frameState.commandList; frameState.commandList = scratchCommandList; - this._content.update(tileset, frameState); + try { + this._content.update(tileset, frameState); + } catch (error) { + this._contentState = Cesium3DTileContentState.FAILED; + throw error; + } scratchCommandList.length = 0; frameState.commandList = savedCommandList; diff --git a/packages/engine/Source/Scene/Cesium3DTileContent.js b/packages/engine/Source/Scene/Cesium3DTileContent.js index c5464c418e0b..714cd0adc3e3 100644 --- a/packages/engine/Source/Scene/Cesium3DTileContent.js +++ b/packages/engine/Source/Scene/Cesium3DTileContent.js @@ -146,12 +146,28 @@ Object.defineProperties(Cesium3DTileContent.prototype, { }, /** - * Gets the promise that will be resolved when the tile's content is ready to render. + * Returns true when the tile's content is ready to render; otherwise false + * + * @memberof Cesium3DTileContent.prototype + * + * @type {boolean} + * @readonly + */ + ready: { + // eslint-disable-next-line getter-return + get: function () { + DeveloperError.throwInstantiationError(); + }, + }, + + /** + * Returns true when the tile's content is ready to render; otherwise false * * @memberof Cesium3DTileContent.prototype * * @type {Promise} * @readonly + * @deprecated */ readyPromise: { // eslint-disable-next-line getter-return diff --git a/packages/engine/Source/Scene/Cesium3DTileContentFactory.js b/packages/engine/Source/Scene/Cesium3DTileContentFactory.js index 23c6b39e8928..6e2186c31ca4 100644 --- a/packages/engine/Source/Scene/Cesium3DTileContentFactory.js +++ b/packages/engine/Source/Scene/Cesium3DTileContentFactory.js @@ -41,7 +41,7 @@ const Cesium3DTileContentFactory = { }, cmpt: function (tileset, tile, resource, arrayBuffer, byteOffset) { // Send in the factory in order to avoid a cyclical dependency - return new Composite3DTileContent( + return Composite3DTileContent.fromTileType( tileset, tile, resource, @@ -51,7 +51,7 @@ const Cesium3DTileContentFactory = { ); }, externalTileset: function (tileset, tile, resource, json) { - return new Tileset3DTileContent(tileset, tile, resource, json); + return Tileset3DTileContent.fromJson(tileset, tile, resource, json); }, geom: function (tileset, tile, resource, arrayBuffer, byteOffset) { return new Geometry3DTileContent( @@ -67,12 +67,13 @@ const Cesium3DTileContentFactory = { tileset, tile, resource, + arrayBuffer, byteOffset ); }, subt: function (tileset, tile, resource, arrayBuffer, byteOffset) { - return new Implicit3DTileContent( + return Implicit3DTileContent.fromSubtreeJson( tileset, tile, resource, @@ -82,7 +83,7 @@ const Cesium3DTileContentFactory = { ); }, subtreeJson: function (tileset, tile, resource, json) { - return new Implicit3DTileContent(tileset, tile, resource, json); + return Implicit3DTileContent.fromSubtreeJson(tileset, tile, resource, json); }, glb: function (tileset, tile, resource, arrayBuffer, byteOffset) { const arrayBufferByteLength = arrayBuffer.byteLength; @@ -92,11 +93,9 @@ const Cesium3DTileContentFactory = { const dataView = new DataView(arrayBuffer, byteOffset); const byteLength = dataView.getUint32(8, true); const glb = new Uint8Array(arrayBuffer, byteOffset, byteLength); - // This should be replace with fromGltfAsync when readyPromise is deprecated across 3D Tiles functions return Model3DTileContent.fromGltf(tileset, tile, resource, glb); }, gltf: function (tileset, tile, resource, json) { - // This should be replace with fromGltfAsync when readyPromise is deprecated across 3D Tiles functions return Model3DTileContent.fromGltf(tileset, tile, resource, json); }, geoJson: function (tileset, tile, resource, json) { diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index baea53463069..02e1904a612e 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -381,15 +381,15 @@ function getVoxelPromise(implicitTileset, tileCoordinates) { }); } -function getSubtreePromise(provider, subtreeCoord) { +async function getSubtreePromise(provider, subtreeCoord) { const implicitTileset = provider._implicitTileset; const subtreeCache = provider._subtreeCache; // First load the subtree to check if the tile is available. // If the subtree has been requested previously it might still be in the cache - const subtree = subtreeCache.find(subtreeCoord); + let subtree = subtreeCache.find(subtreeCoord); if (defined(subtree)) { - return subtree.readyPromise; + return subtree; } const subtreeRelative = implicitTileset.subtreeUriTemplate.getDerivedResource( @@ -401,26 +401,25 @@ function getSubtreePromise(provider, subtreeCoord) { url: subtreeRelative.url, }); - return subtreeResource.fetchArrayBuffer().then(function (arrayBuffer) { - // Check one more time if the subtree is in the cache. - // This could happen if there are two in-flight tile requests from the same - // subtree and one finishes before the other. - let subtree = subtreeCache.find(subtreeCoord); - if (defined(subtree)) { - return subtree.readyPromise; - } + const arrayBuffer = await subtreeResource.fetchArrayBuffer(); + // Check one more time if the subtree is in the cache. + // This could happen if there are two in-flight tile requests from the same + // subtree and one finishes before the other. + subtree = subtreeCache.find(subtreeCoord); + if (defined(subtree)) { + return subtree; + } - const preprocessed = preprocess3DTileContent(arrayBuffer); - subtree = new ImplicitSubtree( - subtreeResource, - preprocessed.jsonPayload, - preprocessed.binaryPayload, - implicitTileset, - subtreeCoord - ); - subtreeCache.addSubtree(subtree); - return subtree.readyPromise; - }); + const preprocessed = preprocess3DTileContent(arrayBuffer); + subtree = await ImplicitSubtree.fromSubtreeJson( + subtreeResource, + preprocessed.jsonPayload, + preprocessed.binaryPayload, + implicitTileset, + subtreeCoord + ); + subtreeCache.addSubtree(subtree); + return subtree; } /** @inheritdoc */ diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index b8c4639a030a..d5c7674a03ae 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -2298,14 +2298,26 @@ function requestContent(tileset, tile) { } const { statistics } = tileset; - const { contentExpired } = tile; - const attemptedRequests = tile.requestContent(); + const contentExpired = tile.contentExpired; - if (attemptedRequests > 0) { - statistics.numberOfAttemptedRequests += attemptedRequests; + const promise = tile.requestContent(); + if (!defined(promise)) { return; } + promise + .then((content) => { + if (!defined(content) || tile.isDestroyed() || tileset.isDestroyed()) { + return; + } + + tileset._processingQueue.push(tile); + ++statistics.numberOfTilesProcessing; + }) + .catch((error) => { + handleTileFailure(error, tileset, tile); + }); + if (contentExpired) { if (tile.hasTilesetContent || tile.hasImplicitContent) { destroySubtree(tileset, tile); @@ -2316,15 +2328,6 @@ function requestContent(tileset, tile) { } tileset._requestedTilesInFlight.push(tile); - - tile.contentReadyToProcessPromise - .then(addToProcessingQueue(tileset, tile)) - .catch(function (e) { - // Any error will propagate through contentReadyPromise and will be handled below - }); - tile.contentReadyPromise - .then(handleTileSuccess(tileset, tile)) - .catch(handleTileFailure(tileset, tile)); } function sortRequestByPriority(a, b) { @@ -2444,43 +2447,30 @@ function requestTiles(tileset) { /** * @private + * @param {Error} error * @param {Cesium3DTileset} tileset * @param {Cesium3DTile} tile - * @returns {Function} */ -function addToProcessingQueue(tileset, tile) { - return function () { - tileset._processingQueue.push(tile); - - ++tileset._statistics.numberOfTilesProcessing; - }; -} +function handleTileFailure(error, tileset, tile) { + if (tileset.isDestroyed()) { + return; + } -/** - * @private - * @param {Cesium3DTileset} tileset - * @param {Cesium3DTile} tile - * @returns {Function} - */ -function handleTileFailure(tileset, tile) { - return function (error) { - if (tile._contentState !== Cesium3DTileContentState.FAILED) { - // If the tile has not failed, the request has been rejected this frame, but will be retried. Do not bubble up the error. - return; - } + let url; + if (!tile.isDestroyed()) { + url = tile._contentResource.url; + } - const url = tile._contentResource.url; - const message = defined(error.message) ? error.message : error.toString(); - if (tileset.tileFailed.numberOfListeners > 0) { - tileset.tileFailed.raiseEvent({ - url: url, - message: message, - }); - } else { - console.log(`A 3D tile failed to load: ${url}`); - console.log(`Error: ${message}`); - } - }; + const message = defined(error.message) ? error.message : error.toString(); + if (tileset.tileFailed.numberOfListeners > 0) { + tileset.tileFailed.raiseEvent({ + url: url, + message: message, + }); + } else { + console.log(`A 3D tile failed to load: ${url}`); + console.log(`Error: ${message}`); + } } /** @@ -2490,27 +2480,7 @@ function handleTileFailure(tileset, tile) { * @returns {Function} */ function handleTileSuccess(tileset, tile) { - return function (content) { - --tileset._statistics.numberOfTilesProcessing; - - if (!defined(content)) { - // A request was cancelled. Do not update successful statistics. The request will be retried. - return; - } - - if (!tile.hasTilesetContent && !tile.hasImplicitContent) { - // RESEARCH_IDEA: ability to unload tiles (without content) for an - // external tileset when all the tiles are unloaded. - tileset._statistics.incrementLoadCounts(tile.content); - ++tileset._statistics.numberOfTilesWithContentReady; - ++tileset._statistics.numberOfLoadedTilesTotal; - - // Add to the tile cache. Previously expired tiles are already in the cache and won't get re-added. - tileset._cache.add(tile); - } - - tileset.tileLoad.raiseEvent(tile); - }; + tileset.tileLoad.raiseEvent(tile); } /** @@ -2523,7 +2493,10 @@ function filterProcessingQueue(tileset) { let removeCount = 0; for (let i = 0; i < tiles.length; ++i) { const tile = tiles[i]; - if (tile._contentState !== Cesium3DTileContentState.PROCESSING) { + if ( + tile.isDestroyed() || + tile._contentState !== Cesium3DTileContentState.PROCESSING + ) { ++removeCount; continue; } @@ -2543,8 +2516,21 @@ function filterProcessingQueue(tileset) { function processTiles(tileset, frameState) { filterProcessingQueue(tileset); const tiles = tileset._processingQueue; + const statistics = tileset._statistics; + let tile; for (let i = 0; i < tiles.length; ++i) { - tiles[i].process(tileset, frameState); + tile = tiles[i]; + try { + tile.process(tileset, frameState); + + if (tile.contentReady) { + --statistics.numberOfTilesProcessing; + handleTileSuccess(tileset, tile, tile.content); + } + } catch (error) { + --statistics.numberOfTilesProcessing; + handleTileFailure(error, tileset, tile); + } } } diff --git a/packages/engine/Source/Scene/Composite3DTileContent.js b/packages/engine/Source/Scene/Composite3DTileContent.js index d1fc612bd182..0c224a38cfad 100644 --- a/packages/engine/Source/Scene/Composite3DTileContent.js +++ b/packages/engine/Source/Scene/Composite3DTileContent.js @@ -17,14 +17,7 @@ import RuntimeError from "../Core/RuntimeError.js"; * * @private */ -function Composite3DTileContent( - tileset, - tile, - resource, - arrayBuffer, - byteOffset, - factory -) { +function Composite3DTileContent(tileset, tile, resource) { this._tileset = tileset; this._tile = tile; this._resource = resource; @@ -32,8 +25,7 @@ function Composite3DTileContent( this._metadata = undefined; this._group = undefined; - - this._readyPromise = initialize(this, arrayBuffer, byteOffset, factory); + this._ready = false; } Object.defineProperties(Composite3DTileContent.prototype, { @@ -130,9 +122,9 @@ Object.defineProperties(Composite3DTileContent.prototype, { }, }, - readyPromise: { + ready: { get: function () { - return this._readyPromise; + return this._ready; }, }, @@ -210,7 +202,14 @@ Object.defineProperties(Composite3DTileContent.prototype, { const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; -function initialize(content, arrayBuffer, byteOffset, factory) { +Composite3DTileContent.fromTileType = async function ( + tileset, + tile, + resource, + arrayBuffer, + byteOffset, + factory +) { byteOffset = defaultValue(byteOffset, 0); const uint8Array = new Uint8Array(arrayBuffer); @@ -231,14 +230,13 @@ function initialize(content, arrayBuffer, byteOffset, factory) { const tilesLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; - const contentPromises = []; + const content = new Composite3DTileContent(tileset, tile, resource); // For caching purposes, models within the composite tile must be // distinguished. To do this, add a query parameter ?compositeIndex=i. // Since composite tiles may contain other composite tiles, check for an // existing prefix and separate them with underscores. e.g. // ?compositeIndex=0_1_1 - const resource = content._resource; let prefix = resource.queryParameters.compositeIndex; if (defined(prefix)) { // We'll be adding another value at the end, so add an underscore. @@ -265,15 +263,16 @@ function initialize(content, arrayBuffer, byteOffset, factory) { }); if (defined(contentFactory)) { - const innerContent = contentFactory( - content._tileset, - content._tile, - childResource, - arrayBuffer, - byteOffset + const innerContent = await Promise.resolve( + contentFactory( + content._tileset, + content._tile, + childResource, + arrayBuffer, + byteOffset + ) ); content._contents.push(innerContent); - contentPromises.push(innerContent.readyPromise); } else { throw new RuntimeError( `Unknown tile content type, ${tileType}, inside Composite tile` @@ -283,10 +282,8 @@ function initialize(content, arrayBuffer, byteOffset, factory) { byteOffset += tileByteLength; } - return Promise.all(contentPromises).then(function () { - return content; - }); -} + return content; +}; /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent @@ -326,9 +323,13 @@ Composite3DTileContent.prototype.applyStyle = function (style) { Composite3DTileContent.prototype.update = function (tileset, frameState) { const contents = this._contents; const length = contents.length; + let ready = true; for (let i = 0; i < length; ++i) { contents[i].update(tileset, frameState); + ready = ready && contents[i].ready; } + + this._ready = ready; }; Composite3DTileContent.prototype.isDestroyed = function () { diff --git a/packages/engine/Source/Scene/Empty3DTileContent.js b/packages/engine/Source/Scene/Empty3DTileContent.js index 59f31a58b1f9..13f427b0e02b 100644 --- a/packages/engine/Source/Scene/Empty3DTileContent.js +++ b/packages/engine/Source/Scene/Empty3DTileContent.js @@ -64,9 +64,9 @@ Object.defineProperties(Empty3DTileContent.prototype, { }, }, - readyPromise: { + ready: { get: function () { - return undefined; + return true; }, }, diff --git a/packages/engine/Source/Scene/Geometry3DTileContent.js b/packages/engine/Source/Scene/Geometry3DTileContent.js index 572cfe4fb68b..f9ea775d9108 100644 --- a/packages/engine/Source/Scene/Geometry3DTileContent.js +++ b/packages/engine/Source/Scene/Geometry3DTileContent.js @@ -42,7 +42,8 @@ function Geometry3DTileContent( this.featurePropertiesDirty = false; this._group = undefined; - this._readyPromise = initialize(this, arrayBuffer, byteOffset); + this._ready = false; + initialize(this, arrayBuffer, byteOffset); } Object.defineProperties(Geometry3DTileContent.prototype, { @@ -96,9 +97,9 @@ Object.defineProperties(Geometry3DTileContent.prototype, { }, }, - readyPromise: { + ready: { get: function () { - return this._readyPromise; + return this._ready; }, }, @@ -224,7 +225,7 @@ function getBatchIds(featureTableJson, featureTableBinary) { if (atLeastOneDefined && atLeastOneUndefined) { throw new RuntimeError( - "If one group of batch ids is defined, then all batch ids must be defined." + "If one group of batch ids is defined, then all batch ids must be defined" ); } @@ -290,7 +291,7 @@ function initialize(content, arrayBuffer, byteOffset) { byteOffset += sizeOfUint32; if (byteLength === 0) { - content._readyPromise.resolve(content); + this._ready = true; return; } @@ -451,9 +452,7 @@ function initialize(content, arrayBuffer, byteOffset) { boundingVolume: content.tile.boundingVolume.boundingVolume, }); - return content._geometries.readyPromise.then(function () { - return content; - }); + return content; } return Promise.resolve(content); @@ -509,8 +508,10 @@ Geometry3DTileContent.prototype.update = function (tileset, frameState) { this._geometries.debugWireframe = this._tileset.debugWireframe; this._geometries.update(frameState); } - if (defined(this._batchTable) && this._geometries._ready) { + + if (defined(this._batchTable) && this._geometries.ready) { this._batchTable.update(tileset, frameState); + this._ready = true; } }; diff --git a/packages/engine/Source/Scene/GltfBufferViewLoader.js b/packages/engine/Source/Scene/GltfBufferViewLoader.js index 4a79f097b39a..a0bb01d6d378 100644 --- a/packages/engine/Source/Scene/GltfBufferViewLoader.js +++ b/packages/engine/Source/Scene/GltfBufferViewLoader.js @@ -212,7 +212,7 @@ function getBufferLoader(bufferViewLoader) { * @private */ GltfBufferViewLoader.prototype.unload = function () { - if (defined(this._bufferLoader)) { + if (defined(this._bufferLoader) && !this._bufferLoader.isDestroyed()) { this._resourceCache.unload(this._bufferLoader); } diff --git a/packages/engine/Source/Scene/GltfImageLoader.js b/packages/engine/Source/Scene/GltfImageLoader.js index 873b0cf1ec7d..91ae40022b81 100644 --- a/packages/engine/Source/Scene/GltfImageLoader.js +++ b/packages/engine/Source/Scene/GltfImageLoader.js @@ -300,7 +300,10 @@ function loadImageFromUri(resource) { * @private */ GltfImageLoader.prototype.unload = function () { - if (defined(this._bufferViewLoader)) { + if ( + defined(this._bufferViewLoader) && + !this._bufferViewLoader.isDestroyed() + ) { this._resourceCache.unload(this._bufferViewLoader); } diff --git a/packages/engine/Source/Scene/GltfIndexBufferLoader.js b/packages/engine/Source/Scene/GltfIndexBufferLoader.js index 563545e21ae6..ca6ace4e0533 100644 --- a/packages/engine/Source/Scene/GltfIndexBufferLoader.js +++ b/packages/engine/Source/Scene/GltfIndexBufferLoader.js @@ -394,7 +394,10 @@ GltfIndexBufferLoader.prototype.unload = function () { const resourceCache = this._resourceCache; - if (defined(this._bufferViewLoader)) { + if ( + defined(this._bufferViewLoader) && + !this._bufferViewLoader.isDestroyed() + ) { resourceCache.unload(this._bufferViewLoader); } diff --git a/packages/engine/Source/Scene/GltfJsonLoader.js b/packages/engine/Source/Scene/GltfJsonLoader.js index 782f3231e3c6..616c28a42651 100644 --- a/packages/engine/Source/Scene/GltfJsonLoader.js +++ b/packages/engine/Source/Scene/GltfJsonLoader.js @@ -281,7 +281,9 @@ GltfJsonLoader.prototype.unload = function () { const bufferLoaders = this._bufferLoaders; const bufferLoadersLength = bufferLoaders.length; for (let i = 0; i < bufferLoadersLength; ++i) { - this._resourceCache.unload(bufferLoaders[i]); + bufferLoaders[i] = + !bufferLoaders[i].isDestroyed() && + this._resourceCache.unload(bufferLoaders[i]); } this._bufferLoaders.length = 0; diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index a32ad01bbefd..24db24b33bc9 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -260,6 +260,8 @@ function GltfLoader(options) { this._geometryCallbacks = []; this._structuralMetadataLoader = undefined; this._loadResourcesPromise = undefined; + this._resourcesLoaded = false; + this._texturesLoaded = false; // In some cases where geometry post-processing is needed (like generating // outlines) new attributes are added that may have GPU resources attached. @@ -295,11 +297,7 @@ Object.defineProperties(GltfLoader.prototype, { * * @memberof GltfLoader.prototype * -<<<<<<< HEAD * @type {ModelComponents.Components} -======= - * @type {string} ->>>>>>> no-ready-promises * @readonly * @private */ @@ -326,7 +324,7 @@ Object.defineProperties(GltfLoader.prototype, { }, }, /** - * true if textures are loaded separately from the other glTF resources. + * Returns true if textures are loaded separately from the other glTF resources. * * @memberof GltfLoader.prototype * @@ -339,16 +337,24 @@ Object.defineProperties(GltfLoader.prototype, { return this._incrementallyLoadTextures; }, }, + /** + * true if textures are loaded, useful when incrementallyLoadTextures is true + * + * @memberof GltfLoader.prototype + * + * @type {boolean} + * @readonly + * @private + */ + texturesLoaded: { + get: function () { + return this._texturesLoaded; + }, + }, }); /** -<<<<<<< HEAD * Loads the gltf object -======= - * Loads the resource. - * @returns {Promise} A promise which resolves to the loader when the resource loading is completed. - * @private ->>>>>>> no-ready-promises */ async function loadGltfJson(loader) { loader._state = GltfLoaderState.LOADING; @@ -425,7 +431,7 @@ async function loadResources(loader, frameState) { // frame. Any promise failures are collected, and will be handled synchronously in process(). Also note that it's fine to call process before a loader is ready // to process or after it has failed; nothing will happen. const gltf = loader.gltfJson; - parse(loader, gltf, supportedImageFormats, frameState); + const promise = parse(loader, gltf, supportedImageFormats, frameState); // All resource loaders have been created, so we can begin processing loader._state = GltfLoaderState.PROCESSING; @@ -437,6 +443,8 @@ async function loadResources(loader, frameState) { ResourceCache.unload(loader._gltfJsonLoader); loader._gltfJsonLoader = undefined; } + + return promise; } /** @@ -550,12 +558,15 @@ GltfLoader.prototype._process = function (frameState) { processLoaders(this, frameState); } - if (this._state === GltfLoaderState.POST_PROCESSING) { + if ( + this._resourcesLoaded && + this._state === GltfLoaderState.POST_PROCESSING + ) { postProcessGeometry(this, frameState.context); this._state = GltfLoaderState.PROCESSED; } - if (this._state === GltfLoaderState.PROCESSED) { + if (this._resourcesLoaded && this._state === GltfLoaderState.PROCESSED) { // The buffer views can be unloaded once the data is copied. unloadBufferViewLoaders(this); @@ -601,6 +612,7 @@ GltfLoader.prototype._processTextures = function (frameState) { } this._textureState = GltfLoaderState.READY; + this._texturesLoaded = true; return true; }; @@ -619,11 +631,13 @@ GltfLoader.prototype.process = function (frameState) { this._state === GltfLoaderState.LOADED && !defined(this._loadResourcesPromise) ) { - this._loadResourcesPromise = loadResources(this, frameState).catch( - (error) => { + this._loadResourcesPromise = loadResources(this, frameState) + .then(() => { + this._resourcesLoaded = true; + }) + .catch((error) => { this._processError = error; - } - ); + }); } if (defined(this._processError)) { @@ -642,11 +656,11 @@ GltfLoader.prototype.process = function (frameState) { throw error; } - let ready = false; if (this._state === GltfLoaderState.FAILED) { - return ready; + return false; } + let ready = false; try { ready = this._process(frameState); } catch (error) { @@ -2646,18 +2660,16 @@ function parse(loader, gltf, supportedImageFormats, frameState) { readyPromises.push.apply(readyPromises, loader._texturesPromises); } - Promise.all(readyPromises).catch((error) => { - // This error will be thrown synchronously during the next call - // to process() - loader._processError = error; - }); + return Promise.all(readyPromises); } function unloadTextures(loader) { const textureLoaders = loader._textureLoaders; const textureLoadersLength = textureLoaders.length; for (let i = 0; i < textureLoadersLength; ++i) { - ResourceCache.unload(textureLoaders[i]); + textureLoaders[i] = + !textureLoaders[i].isDestroyed() && + ResourceCache.unload(textureLoaders[i]); } loader._textureLoaders.length = 0; } @@ -2666,7 +2678,9 @@ function unloadBufferViewLoaders(loader) { const bufferViewLoaders = loader._bufferViewLoaders; const bufferViewLoadersLength = bufferViewLoaders.length; for (let i = 0; i < bufferViewLoadersLength; ++i) { - ResourceCache.unload(bufferViewLoaders[i]); + bufferViewLoaders[i] = + !bufferViewLoaders[i].isDestroyed() && + ResourceCache.unload(bufferViewLoaders[i]); } loader._bufferViewLoaders.length = 0; } @@ -2675,7 +2689,9 @@ function unloadGeometry(loader) { const geometryLoaders = loader._geometryLoaders; const geometryLoadersLength = geometryLoaders.length; for (let i = 0; i < geometryLoadersLength; ++i) { - ResourceCache.unload(geometryLoaders[i]); + geometryLoaders[i] = + !geometryLoaders[i].isDestroyed() && + ResourceCache.unload(geometryLoaders[i]); } loader._geometryLoaders.length = 0; } @@ -2693,7 +2709,10 @@ function unloadGeneratedAttributes(loader) { } function unloadStructuralMetadata(loader) { - if (defined(loader._structuralMetadataLoader)) { + if ( + defined(loader._structuralMetadataLoader) && + !loader._structuralMetadataLoader.isDestroyed() + ) { loader._structuralMetadataLoader.destroy(); loader._structuralMetadataLoader = undefined; } diff --git a/packages/engine/Source/Scene/GltfTextureLoader.js b/packages/engine/Source/Scene/GltfTextureLoader.js index f97d1ec2f27a..277c077bb42a 100644 --- a/packages/engine/Source/Scene/GltfTextureLoader.js +++ b/packages/engine/Source/Scene/GltfTextureLoader.js @@ -369,7 +369,7 @@ GltfTextureLoader.prototype.unload = function () { this._texture.destroy(); } - if (defined(this._imageLoader)) { + if (defined(this._imageLoader) && !this._imageLoader.isDestroyed()) { this._resourceCache.unload(this._imageLoader); } diff --git a/packages/engine/Source/Scene/GltfVertexBufferLoader.js b/packages/engine/Source/Scene/GltfVertexBufferLoader.js index 9835fa6c3177..52cc5e5dc4a8 100644 --- a/packages/engine/Source/Scene/GltfVertexBufferLoader.js +++ b/packages/engine/Source/Scene/GltfVertexBufferLoader.js @@ -452,7 +452,10 @@ GltfVertexBufferLoader.prototype.unload = function () { const resourceCache = this._resourceCache; - if (defined(this._bufferViewLoader)) { + if ( + defined(this._bufferViewLoader) && + !this._bufferViewLoader.isDestroyed() + ) { resourceCache.unload(this._bufferViewLoader); } diff --git a/packages/engine/Source/Scene/Implicit3DTileContent.js b/packages/engine/Source/Scene/Implicit3DTileContent.js index fc6bef81a2f1..8f93e9dc5de3 100644 --- a/packages/engine/Source/Scene/Implicit3DTileContent.js +++ b/packages/engine/Source/Scene/Implicit3DTileContent.js @@ -26,6 +26,7 @@ import parseBoundingVolumeSemantics from "./parseBoundingVolumeSemantics.js"; *

* Implements the {@link Cesium3DTileContent} interface. *

+ * This object is normally not instantiated directly, use {@link Implicit3DTileContent.fromSubtreeJson}. * * @alias Implicit3DTileContent * @constructor @@ -42,20 +43,10 @@ import parseBoundingVolumeSemantics from "./parseBoundingVolumeSemantics.js"; * @private * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy. */ -function Implicit3DTileContent( - tileset, - tile, - resource, - json, - arrayBuffer, - byteOffset -) { +function Implicit3DTileContent(tileset, tile, resource) { //>>includeStart('debug', pragmas.debug); Check.defined("tile.implicitTileset", tile.implicitTileset); Check.defined("tile.implicitCoordinates", tile.implicitCoordinates); - if (defined(json) === defined(arrayBuffer)) { - throw new DeveloperError("One of json and arrayBuffer must be defined."); - } //>>includeEnd('debug'); const implicitTileset = tile.implicitTileset; @@ -81,7 +72,7 @@ function Implicit3DTileContent( ); this._url = subtreeResource.getUrlComponent(true); - this._readyPromise = initialize(this, json, arrayBuffer, byteOffset); + this._ready = false; } Object.defineProperties(Implicit3DTileContent.prototype, { @@ -127,9 +118,9 @@ Object.defineProperties(Implicit3DTileContent.prototype, { }, }, - readyPromise: { + ready: { get: function () { - return this._readyPromise; + return this._ready; }, }, @@ -188,33 +179,58 @@ Object.defineProperties(Implicit3DTileContent.prototype, { * Initialize the implicit content by parsing the subtree resource and setting * up a promise chain to expand the immediate subtree. * - * @param {Implicit3DTileContent} content The implicit content + * @param {Cesium3DTileset} tileset The tileset this content belongs to + * @param {Cesium3DTile} tile The tile this content belongs to. + * @param {Resource} resource The resource for the tileset * @param {object} [json] The JSON containing the subtree. Mutually exclusive with arrayBuffer. * @param {ArrayBuffer} [arrayBuffer] The ArrayBuffer containing a subtree binary. Mutually exclusive with json. * @param {number} [byteOffset=0] The byte offset into the arrayBuffer + * @return {Promise} + * + * @exception {DeveloperError} One of json and arrayBuffer must be defined. + * * @private */ -function initialize(content, json, arrayBuffer, byteOffset) { +Implicit3DTileContent.fromSubtreeJson = async function ( + tileset, + tile, + resource, + json, + arrayBuffer, + byteOffset +) { + //>>includeStart('debug', pragmas.debug); + Check.defined("tile.implicitTileset", tile.implicitTileset); + Check.defined("tile.implicitCoordinates", tile.implicitCoordinates); + if (defined(json) === defined(arrayBuffer)) { + throw new DeveloperError("One of json and arrayBuffer must be defined."); + } + //>>includeEnd('debug'); + byteOffset = defaultValue(byteOffset, 0); let uint8Array; if (defined(arrayBuffer)) { uint8Array = new Uint8Array(arrayBuffer, byteOffset); } - const subtree = new ImplicitSubtree( - content._resource, + const implicitTileset = tile.implicitTileset; + const implicitCoordinates = tile.implicitCoordinates; + + const subtree = await ImplicitSubtree.fromSubtreeJson( + resource, json, uint8Array, - content._implicitTileset, - content._implicitCoordinates + implicitTileset, + implicitCoordinates ); + const content = new Implicit3DTileContent(tileset, tile, resource); + content._implicitSubtree = subtree; - return subtree.readyPromise.then(function () { - expandSubtree(content, subtree); - return content; - }); -} + expandSubtree(content, subtree); + content._ready = true; + return content; +}; /** * Expand a single subtree placeholder tile. This transcodes the subtree into diff --git a/packages/engine/Source/Scene/ImplicitSubtree.js b/packages/engine/Source/Scene/ImplicitSubtree.js index ea8060f1673d..97133ffa17db 100644 --- a/packages/engine/Source/Scene/ImplicitSubtree.js +++ b/packages/engine/Source/Scene/ImplicitSubtree.js @@ -22,34 +22,24 @@ import ResourceCache from "./ResourceCache.js"; * Subtrees also handle content metadata and metadata about the subtree itself. *

* + * This object is normally not instantiated directly, use {@link ImplicitSubtree.fromSubtreeJson}. + * * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_metadata#implicit-tile-properties|Implicit Tile Properties in the 3DTILES_metadata specification} + * @see ImplicitSubtree.fromSubtreeJson * * @alias ImplicitSubtree * @constructor * * @param {Resource} resource The resource for this subtree. This is used for fetching external buffers as needed. - * @param {object} [json] The JSON object for this subtree. Mutually exclusive with subtreeView. - * @param {Uint8Array} [subtreeView] The contents of a subtree binary in a Uint8Array. Mutually exclusive with json. * @param {ImplicitTileset} implicitTileset The implicit tileset. This includes information about the size of subtrees * @param {ImplicitTileCoordinates} implicitCoordinates The coordinates of the subtree's root tile. * - * @exception {DeveloperError} One of json and subtreeView must be defined. - * * @private * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy. */ -function ImplicitSubtree( - resource, - json, - subtreeView, - implicitTileset, - implicitCoordinates -) { +function ImplicitSubtree(resource, implicitTileset, implicitCoordinates) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("resource", resource); - if (defined(json) === defined(subtreeView)) { - throw new DeveloperError("One of json and subtreeView must be defined."); - } Check.typeOf.object("implicitTileset", implicitTileset); Check.typeOf.object("implicitCoordinates", implicitCoordinates); //>>includeEnd('debug'); @@ -77,21 +67,21 @@ function ImplicitSubtree( this._tileJumpBuffer = undefined; this._contentJumpBuffers = []; - this._readyPromise = initialize(this, json, subtreeView, implicitTileset); + this._ready = false; } Object.defineProperties(ImplicitSubtree.prototype, { /** - * A promise that resolves once all necessary availability buffers + * Returns true once all necessary availability buffers * are loaded. * - * @type {Promise} + * @type {boolean} * @readonly * @private */ - readyPromise: { + ready: { get: function () { - return this._readyPromise; + return this._ready; }, }, @@ -309,16 +299,40 @@ ImplicitSubtree.prototype.getParentMortonIndex = function (mortonIndex) { /** * Parse all relevant information out of the subtree. This fetches any - * external buffers that are used by the implicit tileset. When finished, - * it resolves/rejects subtree.readyPromise. + * external buffers that are used by the implicit tileset. * - * @param {ImplicitSubtree} subtree The subtree + * @param {Resource} resource The resource for this subtree. This is used for fetching external buffers as needed. * @param {object} [json] The JSON object for this subtree. If parsing from a binary subtree file, this will be undefined. * @param {Uint8Array} [subtreeView] The contents of the subtree binary * @param {ImplicitTileset} implicitTileset The implicit tileset this subtree belongs to. + * @param {ImplicitTileCoordinates} implicitCoordinates The coordinates of the subtree's root tile. + * @return {Promise} The created subtree * @private + * + * @exception {DeveloperError} One of json and subtreeView must be defined. */ -function initialize(subtree, json, subtreeView, implicitTileset) { +ImplicitSubtree.fromSubtreeJson = async function ( + resource, + json, + subtreeView, + implicitTileset, + implicitCoordinates +) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("resource", resource); + if (defined(json) === defined(subtreeView)) { + throw new DeveloperError("One of json and subtreeView must be defined."); + } + Check.typeOf.object("implicitTileset", implicitTileset); + Check.typeOf.object("implicitCoordinates", implicitCoordinates); + //>>includeEnd('debug'); + + const subtree = new ImplicitSubtree( + resource, + implicitTileset, + implicitCoordinates + ); + let chunks; if (defined(json)) { chunks = { @@ -409,26 +423,25 @@ function initialize(subtree, json, subtreeView, implicitTileset) { markActiveMetadataBufferViews(contentPropertyTableJson, bufferViewHeaders); } - return requestActiveBuffers(subtree, bufferHeaders, chunks.binary).then( - function (buffersU8) { - const bufferViewsU8 = parseActiveBufferViews( - bufferViewHeaders, - buffersU8 - ); - parseAvailability(subtree, subtreeJson, implicitTileset, bufferViewsU8); + const buffersU8 = await requestActiveBuffers( + subtree, + bufferHeaders, + chunks.binary + ); + const bufferViewsU8 = parseActiveBufferViews(bufferViewHeaders, buffersU8); + parseAvailability(subtree, subtreeJson, implicitTileset, bufferViewsU8); - if (defined(tilePropertyTableJson)) { - parseTileMetadataTable(subtree, implicitTileset, bufferViewsU8); - makeTileJumpBuffer(subtree); - } + if (defined(tilePropertyTableJson)) { + parseTileMetadataTable(subtree, implicitTileset, bufferViewsU8); + makeTileJumpBuffer(subtree); + } - parseContentMetadataTables(subtree, implicitTileset, bufferViewsU8); - makeContentJumpBuffers(subtree); + parseContentMetadataTables(subtree, implicitTileset, bufferViewsU8); + makeContentJumpBuffers(subtree); - return subtree; - } - ); -} + subtree._ready = true; + return subtree; +}; /** * A helper object for storing the two parts of the subtree binary @@ -708,7 +721,7 @@ function requestActiveBuffers(subtree, bufferHeaders, internalBuffer) { }); } -function requestExternalBuffer(subtree, bufferHeader) { +async function requestExternalBuffer(subtree, bufferHeader) { const baseResource = subtree._resource; const bufferResource = baseResource.getDerivedResource({ url: bufferHeader.uri, @@ -719,9 +732,17 @@ function requestExternalBuffer(subtree, bufferHeader) { }); subtree._bufferLoader = bufferLoader; - return bufferLoader.load().then(function (bufferLoader) { - return bufferLoader.typedArray; - }); + try { + await bufferLoader.load(); + } catch (error) { + if (bufferLoader.isDestroyed()) { + return; + } + + throw error; + } + + return bufferLoader.typedArray; } /** diff --git a/packages/engine/Source/Scene/Model/B3dmLoader.js b/packages/engine/Source/Scene/Model/B3dmLoader.js index ebdb1bbf7115..7385f18a690d 100644 --- a/packages/engine/Source/Scene/Model/B3dmLoader.js +++ b/packages/engine/Source/Scene/Model/B3dmLoader.js @@ -129,34 +129,17 @@ if (defined(Object.create)) { Object.defineProperties(B3dmLoader.prototype, { /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. + * true if textures are loaded, useful when incrementallyLoadTextures is true * * @memberof B3dmLoader.prototype * - * @type {Promise|undefined} + * @type {boolean} * @readonly * @private */ - promise: { + texturesLoaded: { get: function () { - return this._promise; - }, - }, - - /** - * A promise that resolves when all textures are loaded. - * When incrementallyLoadTextures is true this may resolve after - * promise resolves. - * - * @memberof B3dmLoader.prototype - * - * @type {Promise} - * @readonly - * @private - */ - texturesLoadedPromise: { - get: function () { - return this._gltfLoader.texturesLoadedPromise; + return this._gltfLoader?.texturesLoaded; }, }, /** @@ -196,6 +179,10 @@ Object.defineProperties(B3dmLoader.prototype, { * @private */ B3dmLoader.prototype.load = function () { + if (defined(this._promise)) { + return this._promise; + } + const b3dm = B3dmParser.parse(this._arrayBuffer, this._byteOffset); let batchLength = b3dm.batchLength; @@ -246,37 +233,21 @@ B3dmLoader.prototype.load = function () { this._state = B3dmLoaderState.LOADING; const that = this; - gltfLoader.load(); - this._promise = gltfLoader.promise + this._promise = gltfLoader + .load() .then(function () { if (that.isDestroyed()) { return; } - const components = gltfLoader.components; - - // Combine the RTC_CENTER transform from the b3dm and the CESIUM_RTC - // transform from the glTF. In practice usually only one or the - // other is supplied. If they don't exist the transforms will - // be identity matrices. - components.transform = Matrix4.multiplyTransformation( - that._transform, - components.transform, - components.transform - ); - createStructuralMetadata(that, components); - that._components = components; - - // Now that we have the parsed components, we can release the array buffer - that._arrayBuffer = undefined; - - that._state = B3dmLoaderState.READY; + that._state = B3dmLoaderState.PROCESSING; return that; }) .catch(function (error) { if (that.isDestroyed()) { return; } + return handleError(that, error); }); @@ -296,13 +267,38 @@ B3dmLoader.prototype.process = function (frameState) { Check.typeOf.object("frameState", frameState); //>>includeEnd('debug'); - if (this._state === B3dmLoaderState.LOADING) { - this._state = B3dmLoaderState.PROCESSING; + if (this._state === B3dmLoaderState.READY) { + return true; + } + + if (this._state !== B3dmLoaderState.PROCESSING) { + return false; } - if (this._state === B3dmLoaderState.PROCESSING) { - this._gltfLoader.process(frameState); + const ready = this._gltfLoader.process(frameState); + if (!ready) { + return false; } + + const components = this._gltfLoader.components; + + // Combine the RTC_CENTER transform from the b3dm and the CESIUM_RTC + // transform from the glTF. In practice usually only one or the + // other is supplied. If they don't exist the transforms will + // be identity matrices. + components.transform = Matrix4.multiplyTransformation( + this._transform, + components.transform, + components.transform + ); + createStructuralMetadata(this, components); + this._components = components; + + // Now that we have the parsed components, we can release the array buffer + this._arrayBuffer = undefined; + + this._state = B3dmLoaderState.READY; + return true; }; function createStructuralMetadata(loader, components) { @@ -368,7 +364,7 @@ function processNode(node) { } B3dmLoader.prototype.unload = function () { - if (defined(this._gltfLoader)) { + if (defined(this._gltfLoader) && !this._gltfLoader.isDestroyed()) { this._gltfLoader.unload(); } diff --git a/packages/engine/Source/Scene/Model/GeoJsonLoader.js b/packages/engine/Source/Scene/Model/GeoJsonLoader.js index 239edbb679cc..5bc67d5ec9ca 100644 --- a/packages/engine/Source/Scene/Model/GeoJsonLoader.js +++ b/packages/engine/Source/Scene/Model/GeoJsonLoader.js @@ -48,8 +48,6 @@ function GeoJsonLoader(options) { //>>includeEnd('debug'); this._geoJson = options.geoJson; - this._promise = undefined; - this._process = function (loader, frameState) {}; this._components = undefined; } @@ -59,20 +57,6 @@ if (defined(Object.create)) { } Object.defineProperties(GeoJsonLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource has not yet started loading. - * - * @memberof GeoJsonLoader.prototype - * - * @type {Promise|Undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource. * @@ -109,21 +93,7 @@ Object.defineProperties(GeoJsonLoader.prototype, { * @private */ GeoJsonLoader.prototype.load = function () { - const loader = this; - const promise = new Promise(function (resolve) { - loader._process = function (loader, frameState) { - if (defined(loader._components)) { - return; - } - - loader._components = parse(loader._geoJson, frameState); - loader._geoJson = undefined; - resolve(loader); - }; - }); - - this._promise = promise; - return promise; + return Promise.resolve(this); }; /** @@ -137,7 +107,13 @@ GeoJsonLoader.prototype.process = function (frameState) { Check.typeOf.object("frameState", frameState); //>>includeEnd('debug'); - this._process(this, frameState); + if (defined(this._components)) { + return true; + } + + this._components = parse(this._geoJson, frameState); + this._geoJson = undefined; + return true; }; function ParsedFeature() { diff --git a/packages/engine/Source/Scene/Model/I3dmLoader.js b/packages/engine/Source/Scene/Model/I3dmLoader.js index 0bb74d099817..ceffc90f272e 100644 --- a/packages/engine/Source/Scene/Model/I3dmLoader.js +++ b/packages/engine/Source/Scene/Model/I3dmLoader.js @@ -116,9 +116,6 @@ function I3dmLoader(options) { this._promise = undefined; this._gltfLoader = undefined; - this._gltfLoaderPromise = undefined; - this._process = function (loader, frameState) {}; - this._postProcess = function (loader, frameState) {}; // Instanced attributes are initially parsed as typed arrays, but if they // do not need to be further processed (e.g. turned into transform matrices), @@ -140,34 +137,17 @@ if (defined(Object.create)) { Object.defineProperties(I3dmLoader.prototype, { /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. + * true if textures are loaded, useful when incrementallyLoadTextures is true * - * @memberof I3dmLoader.prototype - * - * @type {Promise|undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, - - /** - * A promise that resolves when all textures are loaded. - * When incrementallyLoadTextures is true this may resolve after - * promise resolves. + * @memberof I3dmLoader.prototype * - * @memberof I3dmLoader.prototype - * - * @type {Promise} + * @type {boolean} * @readonly * @private */ - texturesLoadedPromise: { + texturesLoaded: { get: function () { - return this._gltfLoader.texturesLoadedPromise; + return this._gltfLoader?.texturesLoaded; }, }, /** @@ -208,6 +188,10 @@ Object.defineProperties(I3dmLoader.prototype, { * @private */ I3dmLoader.prototype.load = function () { + if (defined(this._promise)) { + return this._promise; + } + // Parse the i3dm into its various sections. const i3dm = I3dmParser.parse(this._arrayBuffer, this._byteOffset); @@ -282,53 +266,21 @@ I3dmLoader.prototype.load = function () { this._gltfLoader = gltfLoader; this._state = I3dmLoaderState.LOADING; - gltfLoader.load(); - - const that = this; - const processPromise = new Promise(function (resolve) { - that._process = function (loader, frameState) { - loader._gltfLoader.process(frameState); - }; - - that._postProcess = function (loader, frameState) { - const gltfLoader = loader._gltfLoader; - const components = gltfLoader.components; - - // Combine the RTC_CENTER transform from the i3dm and the CESIUM_RTC - // transform from the glTF. In practice CESIUM_RTC is not set for - // instanced models but multiply the transforms just in case. - components.transform = Matrix4.multiplyTransformation( - loader._transform, - components.transform, - components.transform - ); - - createInstances(loader, components, frameState); - createStructuralMetadata(loader, components); - loader._components = components; - - // Now that we have the parsed components, we can release the array buffer - loader._arrayBuffer = undefined; - - loader._state = I3dmLoaderState.READY; - resolve(loader); - }; - }); - - this._promise = gltfLoader.promise - .then(function () { - if (that.isDestroyed()) { + this._promise = gltfLoader + .load() + .then(() => { + if (this.isDestroyed()) { return; } - that._state = I3dmLoaderState.POST_PROCESSING; - return processPromise; + this._state = I3dmLoaderState.PROCESSING; + return this; }) - .catch(function (error) { - if (that.isDestroyed()) { + .catch((error) => { + if (this.isDestroyed()) { return; } - return handleError(that, error); + throw handleError(this, error); }); return this._promise; @@ -338,8 +290,7 @@ function handleError(i3dmLoader, error) { i3dmLoader.unload(); i3dmLoader._state = I3dmLoaderState.FAILED; const errorMessage = "Failed to load i3dm"; - error = i3dmLoader.getError(errorMessage, error); - return Promise.reject(error); + return i3dmLoader.getError(errorMessage, error); } I3dmLoader.prototype.process = function (frameState) { @@ -347,17 +298,40 @@ I3dmLoader.prototype.process = function (frameState) { Check.typeOf.object("frameState", frameState); //>>includeEnd('debug'); - if (this._state === I3dmLoaderState.LOADING) { - this._state = I3dmLoaderState.PROCESSING; + if (this._state === I3dmLoaderState.READY) { + return true; } + const gltfLoader = this._gltfLoader; + let ready = false; if (this._state === I3dmLoaderState.PROCESSING) { - this._process(this, frameState); + ready = gltfLoader.process(frameState); } - if (this._state === I3dmLoaderState.POST_PROCESSING) { - this._postProcess(this, frameState); + if (!ready) { + return false; } + + const components = gltfLoader.components; + + // Combine the RTC_CENTER transform from the i3dm and the CESIUM_RTC + // transform from the glTF. In practice CESIUM_RTC is not set for + // instanced models but multiply the transforms just in case. + components.transform = Matrix4.multiplyTransformation( + this._transform, + components.transform, + components.transform + ); + + createInstances(this, components, frameState); + createStructuralMetadata(this, components); + this._components = components; + + // Now that we have the parsed components, we can release the array buffer + this._arrayBuffer = undefined; + + this._state = I3dmLoaderState.READY; + return true; }; function createStructuralMetadata(loader, components) { @@ -895,7 +869,7 @@ I3dmLoader.prototype.isUnloaded = function () { }; I3dmLoader.prototype.unload = function () { - if (defined(this._gltfLoader)) { + if (defined(this._gltfLoader) && !this._gltfLoader.isDestroyed()) { this._gltfLoader.unload(); } diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 2dbbfd094326..d7fbad7f790e 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -534,7 +534,7 @@ function handleError(model, error) { return; } - console.error(error); + console.log(error); } function createModelFeatureTables(model, structuralMetadata) { @@ -679,6 +679,21 @@ Object.defineProperties(Model.prototype, { }, }, + /** + * Returns true if textures are loaded separately from the other glTF resources. + * + * @memberof GltfLoader.prototype + * + * @type {boolean} + * @readonly + * @private + */ + incrementallyLoadTextures: { + get: function () { + return defaultValue(this._loader.incrementallyLoadTextures, false); + }, + }, + /** * Gets an event that, if {@link Model.incrementallyLoadTextures} is true, is raised when the model textures are loaded and ready for rendering, i.e. when the external resources * have been downloaded and the WebGL resources are created. Event listeners @@ -711,7 +726,7 @@ Object.defineProperties(Model.prototype, { get: function () { deprecationWarning( "Model.readyPromise", - "Model.readyPromise was deprecated in CesiumJS 1.102. It will be removed in 1.104. Use Model.fromGltfAsync and Model.readyEvent instead." + "Model.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use Model.fromGltfAsync and Model.readyEvent instead." ); return this._readyPromise; }, @@ -734,7 +749,7 @@ Object.defineProperties(Model.prototype, { get: function () { deprecationWarning( "Model.texturesLoadedPromise", - "Model.texturesLoadedPromise was deprecated in CesiumJS 1.102. It will be removed in 1.104. Use Model.fromGltfAsync and Model.texturesReadyEvent instead." + "Model.texturesLoadedPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use Model.fromGltfAsync and Model.texturesReadyEvent instead." ); return this._texturesLoadedPromise; }, @@ -1925,8 +1940,11 @@ Model.prototype.update = function (frameState) { return; } - if (this._loader.incrementallyLoadTextures && !this._texturesLoaded) { - // TODO: Check if textures are actually ready + if ( + this._loader.incrementallyLoadTextures && + !this._texturesLoaded && + this._loader.texturesLoaded + ) { // Re-run the pipeline so texture memory statistics are re-computed this.resetDrawCommands(); @@ -2763,7 +2781,7 @@ Model.prototype.destroyModelResources = function () { Model.fromGltf = function (options) { deprecationWarning( "Model.fromGltf", - "Model.fromGltf was deprecated in CesiumJS 1.102. It will be removed in 1.104. Use Model.fromGltfAsync instead." + "Model.fromGltf was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use Model.fromGltfAsync instead." ); options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -2995,7 +3013,7 @@ Model.fromGltfAsync = async function (options) { /* * @private */ -Model.fromB3dm = function (options) { +Model.fromB3dm = async function (options) { const loaderOptions = { b3dmResource: options.resource, arrayBuffer: options.arrayBuffer, @@ -3013,15 +3031,22 @@ Model.fromB3dm = function (options) { const loader = new B3dmLoader(loaderOptions); - const modelOptions = makeModelOptions(loader, ModelType.TILE_B3DM, options); - const model = new Model(modelOptions); - return model; + try { + await loader.load(); + + const modelOptions = makeModelOptions(loader, ModelType.TILE_B3DM, options); + const model = new Model(modelOptions); + return model; + } catch (error) { + loader.destroy(); + throw error; + } }; /** * @private */ -Model.fromPnts = function (options) { +Model.fromPnts = async function (options) { const loaderOptions = { arrayBuffer: options.arrayBuffer, byteOffset: options.byteOffset, @@ -3029,15 +3054,21 @@ Model.fromPnts = function (options) { }; const loader = new PntsLoader(loaderOptions); - const modelOptions = makeModelOptions(loader, ModelType.TILE_PNTS, options); - const model = new Model(modelOptions); - return model; + try { + await loader.load(); + const modelOptions = makeModelOptions(loader, ModelType.TILE_PNTS, options); + const model = new Model(modelOptions); + return model; + } catch (error) { + loader.destroy(); + throw error; + } }; /* * @private */ -Model.fromI3dm = function (options) { +Model.fromI3dm = async function (options) { const loaderOptions = { i3dmResource: options.resource, arrayBuffer: options.arrayBuffer, @@ -3053,15 +3084,22 @@ Model.fromI3dm = function (options) { }; const loader = new I3dmLoader(loaderOptions); - const modelOptions = makeModelOptions(loader, ModelType.TILE_I3DM, options); - const model = new Model(modelOptions); - return model; + try { + await loader.load(); + + const modelOptions = makeModelOptions(loader, ModelType.TILE_I3DM, options); + const model = new Model(modelOptions); + return model; + } catch (error) { + loader.destroy(); + throw error; + } }; /* * @private */ -Model.fromGeoJson = function (options) { +Model.fromGeoJson = async function (options) { const loaderOptions = { geoJson: options.geoJson, }; diff --git a/packages/engine/Source/Scene/Model/Model3DTileContent.js b/packages/engine/Source/Scene/Model/Model3DTileContent.js index 51744b06bf2a..52c51879f4b8 100644 --- a/packages/engine/Source/Scene/Model/Model3DTileContent.js +++ b/packages/engine/Source/Scene/Model/Model3DTileContent.js @@ -14,6 +14,8 @@ import Model from "./Model.js"; *

* Implements the {@link Cesium3DTileContent} interface. *

+ * This object is normally not instantiated directly, use {@link Model3DTileContent.fromGltf}, {@link Model3DTileContent.fromB3dm}, {@link Model3DTileContent.fromI3dm}, {@link Model3DTileContent.fromPnts}, or {@link Model3DTileContent.fromGeoJson}. + * * @alias Model3DTileContent * @constructor * @private @@ -24,9 +26,9 @@ function Model3DTileContent(tileset, tile, resource) { this._resource = resource; this._model = undefined; - this._readyPromise = undefined; this._metadata = undefined; this._group = undefined; + this._ready = false; } Object.defineProperties(Model3DTileContent.prototype, { @@ -83,9 +85,9 @@ Object.defineProperties(Model3DTileContent.prototype, { }, }, - readyPromise: { + ready: { get: function () { - return this._readyPromise; + return this._ready; }, }, @@ -244,6 +246,15 @@ Model3DTileContent.prototype.update = function (tileset, frameState) { } model.update(frameState); + + if (!this._ready && model.ready) { + // Animation can only be added once the model is ready + model.activeAnimations.addAll({ + loop: ModelAnimationLoop.REPEAT, + }); + + this._ready = true; + } }; Model3DTileContent.prototype.isDestroyed = function () { @@ -255,7 +266,7 @@ Model3DTileContent.prototype.destroy = function () { return destroyObject(this); }; -Model3DTileContent.fromGltf = function (tileset, tile, resource, gltf) { +Model3DTileContent.fromGltf = async function (tileset, tile, resource, gltf) { const content = new Model3DTileContent(tileset, tile, resource); const additionalOptions = { @@ -276,21 +287,13 @@ Model3DTileContent.fromGltf = function (tileset, tile, resource, gltf) { modelOptions.classificationType = classificationType; - // This should be removed when readyPromise is deprecated across 3D Tiles functions - const model = Model.fromGltf(modelOptions); + const model = await Model.fromGltfAsync(modelOptions); content._model = model; - // Include the animation setup in the ready promise to avoid an uncaught exception - content._readyPromise = model.readyPromise.then(function (model) { - model.activeAnimations.addAll({ - loop: ModelAnimationLoop.REPEAT, - }); - return model; - }); return content; }; -Model3DTileContent.fromB3dm = function ( +Model3DTileContent.fromB3dm = async function ( tileset, tile, resource, @@ -318,15 +321,13 @@ Model3DTileContent.fromB3dm = function ( modelOptions.classificationType = classificationType; - const model = Model.fromB3dm(modelOptions); + const model = await Model.fromB3dm(modelOptions); content._model = model; - // This should be removed when readyPromise is deprecated across 3D Tiles functions - content._readyPromise = model._readyPromise; return content; }; -Model3DTileContent.fromI3dm = function ( +Model3DTileContent.fromI3dm = async function ( tileset, tile, resource, @@ -348,15 +349,13 @@ Model3DTileContent.fromI3dm = function ( additionalOptions ); - const model = Model.fromI3dm(modelOptions); + const model = await Model.fromI3dm(modelOptions); content._model = model; - // This should be removed when readyPromise is deprecated across 3D Tiles functions - content._readyPromise = model._readyPromise; return content; }; -Model3DTileContent.fromPnts = function ( +Model3DTileContent.fromPnts = async function ( tileset, tile, resource, @@ -377,15 +376,18 @@ Model3DTileContent.fromPnts = function ( content, additionalOptions ); - const model = Model.fromPnts(modelOptions); + const model = await Model.fromPnts(modelOptions); content._model = model; - // This should be removed when readyPromise is deprecated across 3D Tiles functions - content._readyPromise = model._readyPromise; return content; }; -Model3DTileContent.fromGeoJson = function (tileset, tile, resource, geoJson) { +Model3DTileContent.fromGeoJson = async function ( + tileset, + tile, + resource, + geoJson +) { const content = new Model3DTileContent(tileset, tile, resource); const additionalOptions = { @@ -399,10 +401,8 @@ Model3DTileContent.fromGeoJson = function (tileset, tile, resource, geoJson) { content, additionalOptions ); - const model = Model.fromGeoJson(modelOptions); + const model = await Model.fromGeoJson(modelOptions); content._model = model; - // This should be removed when readyPromise is deprecated across 3D Tiles functions - content._readyPromise = model._readyPromise; return content; }; diff --git a/packages/engine/Source/Scene/Model/PntsLoader.js b/packages/engine/Source/Scene/Model/PntsLoader.js index ac6013e1cd7f..2565dd01a420 100644 --- a/packages/engine/Source/Scene/Model/PntsLoader.js +++ b/packages/engine/Source/Scene/Model/PntsLoader.js @@ -66,7 +66,7 @@ function PntsLoader(options) { this._decodedAttributes = undefined; this._promise = undefined; - this._process = function (frameState) {}; + this._error = undefined; this._state = ResourceLoaderState.UNLOADED; this._buffers = []; @@ -81,20 +81,6 @@ if (defined(Object.create)) { } Object.defineProperties(PntsLoader.prototype, { - /** - * A promise that resolves to the resource when the resource is ready, or undefined if the resource hasn't started loading. - * - * @memberof PntsLoader.prototype - * - * @type {Promise|undefined} - * @readonly - * @private - */ - promise: { - get: function () { - return this._promise; - }, - }, /** * The cache key of the resource * @@ -148,28 +134,36 @@ Object.defineProperties(PntsLoader.prototype, { * @private */ PntsLoader.prototype.load = function () { + if (defined(this._promise)) { + return this._promise; + } + this._parsedContent = PntsParser.parse(this._arrayBuffer, this._byteOffset); this._state = ResourceLoaderState.PROCESSING; - const loader = this; - this._promise = new Promise(function (resolve, reject) { - loader._process = function (frameState) { - if (loader._state === ResourceLoaderState.PROCESSING) { - if (defined(loader._decodePromise)) { - return; - } - - const decodePromise = decodeDraco(loader, frameState.context); - if (defined(decodePromise)) { - decodePromise.then(resolve).catch(reject); - } - } - }; - }); + this._promise = Promise.resolve(this); }; PntsLoader.prototype.process = function (frameState) { - this._process(frameState); + if (defined(this._error)) { + const error = this._error; + this._error = undefined; + throw error; + } + + if (this._state === ResourceLoaderState.READY) { + return true; + } + + if (this._state === ResourceLoaderState.PROCESSING) { + if (defined(this._decodePromise)) { + return false; + } + + this._decodePromise = decodeDraco(this, frameState.context); + } + + return false; }; function decodeDraco(loader, context) { @@ -207,7 +201,8 @@ function decodeDraco(loader, context) { loader.unload(); loader._state = ResourceLoaderState.FAILED; const errorMessage = "Failed to load Draco pnts"; - return Promise.reject(loader.getError(errorMessage, error)); + // This error will be thrown next time process is called; + loader._error = loader.getError(errorMessage, error); }); } diff --git a/packages/engine/Source/Scene/Multiple3DTileContent.js b/packages/engine/Source/Scene/Multiple3DTileContent.js index 51b20bb93a45..c5ebb7e8a819 100644 --- a/packages/engine/Source/Scene/Multiple3DTileContent.js +++ b/packages/engine/Source/Scene/Multiple3DTileContent.js @@ -54,6 +54,7 @@ function Multiple3DTileContent(tileset, tile, tilesetResource, contentsJson) { const contentCount = this._innerContentHeaders.length; this._arrayFetchPromises = new Array(contentCount); this._requests = new Array(contentCount); + this._ready = false; this._innerContentResources = new Array(contentCount); this._serverKeys = new Array(contentCount); @@ -70,9 +71,6 @@ function Multiple3DTileContent(tileset, tile, tilesetResource, contentsJson) { this._innerContentResources[i] = contentResource; this._serverKeys[i] = serverKey; } - - // undefined until the first time requests are scheduled - this._contentsFetchedPromise = undefined; } Object.defineProperties(Multiple3DTileContent.prototype, { @@ -213,35 +211,13 @@ Object.defineProperties(Multiple3DTileContent.prototype, { }, }, - /** - * Part of the {@link Cesium3DTileContent} interface. This behaves - * differently for Multiple3DTileContent: it returns - * undefined if the inner contents have not yet been created. This - * is handled specially in {@link Cesium3DTile}. - * - * @memberof Multiple3DTileContent.prototype - * - * @type {Promise|undefined} - * @readonly - * - * @private - */ - readyPromise: { + ready: { get: function () { - // The contents haven't been created yet, short-circuit. The tile - // will handle this as a special case if (!this._contentsCreated) { - return undefined; + return false; } - const readyPromises = this._contents.map(function (content) { - return content.readyPromise; - }); - - const that = this; - return Promise.all(readyPromises).then(function () { - return that; - }); + return this._ready; }, }, @@ -338,21 +314,6 @@ Object.defineProperties(Multiple3DTileContent.prototype, { }); }, }, - - /** - * A promise that resolves when all of the inner contents have been fetched. - * This promise is undefined until the first frame where all array buffer - * requests have been scheduled. - * @memberof Multiple3DTileContent.prototype - * - * @type {Promise} - * @private - */ - contentsFetchedPromise: { - get: function () { - return this._contentsFetchedPromise; - }, - }, }); function updatePendingRequests(multipleContents, deltaRequestCount) { @@ -386,7 +347,7 @@ function cancelPendingRequests(multipleContents, originalContentState) { * requests are successfully scheduled. *

* - * @return {number} The number of attempted requests that were unable to be scheduled. + * @return {Promise|undefined} A promise that resolves when the request completes, or undefined if there is no request needed, or the request cannot be scheduled. * @private */ Multiple3DTileContent.prototype.requestInnerContents = function () { @@ -395,26 +356,26 @@ Multiple3DTileContent.prototype.requestInnerContents = function () { // if we can schedule all the requests at once. If not, no requests are // scheduled if (!canScheduleAllRequests(this._serverKeys)) { - return this._serverKeys.length; + this.tileset.statistics.numberOfAttemptedRequests += this._serverKeys.length; + return; } const contentHeaders = this._innerContentHeaders; updatePendingRequests(this, contentHeaders.length); + const originalCancelCount = this._cancelCount; for (let i = 0; i < contentHeaders.length; i++) { // The cancel count is needed to avoid a race condition where a content // is canceled multiple times. this._arrayFetchPromises[i] = requestInnerContent( this, i, - this._cancelCount, + originalCancelCount, this._tile._contentState ); } - this._contentsFetchedPromise = createInnerContents(this); - - return 0; + return createInnerContents(this); }; /** @@ -476,28 +437,39 @@ function requestInnerContent( const promise = contentResource.fetchArrayBuffer(); if (!defined(promise)) { - return Promise.resolve(undefined); + return; } return promise .then(function (arrayBuffer) { - // Short circuit if another inner content was canceled. + // Pending requests have already been canceled. if (originalCancelCount < multipleContents._cancelCount) { - return undefined; + return; + } + + if ( + contentResource.request.cancelled || + contentResource.request.state === RequestState.CANCELLED + ) { + cancelPendingRequests(multipleContents, originalContentState); + return; } updatePendingRequests(multipleContents, -1); return arrayBuffer; }) .catch(function (error) { - // Short circuit if another inner content was canceled. + // Pending requests have already been canceled. if (originalCancelCount < multipleContents._cancelCount) { - return undefined; + return; } - if (contentResource.request.state === RequestState.CANCELLED) { + if ( + contentResource.request.cancelled || + contentResource.request.state === RequestState.CANCELLED + ) { cancelPendingRequests(multipleContents, originalContentState); - return undefined; + return; } updatePendingRequests(multipleContents, -1); @@ -505,42 +477,54 @@ function requestInnerContent( }); } -function createInnerContents(multipleContents) { +async function createInnerContents(multipleContents) { const originalCancelCount = multipleContents._cancelCount; - return Promise.all(multipleContents._arrayFetchPromises).then(function ( - arrayBuffers - ) { - // Short circuit if inner contents were canceled - if (originalCancelCount < multipleContents._cancelCount) { - return undefined; + const arrayBuffers = await Promise.all(multipleContents._arrayFetchPromises); + // Request have been cancelled + if (originalCancelCount < multipleContents._cancelCount) { + return; + } + + const createPromise = async (arrayBuffer, i) => { + if (!defined(arrayBuffer)) { + // Content was not fetched. The error was handled in + // the fetch promise. Return undefined to indicate partial failure. + return; } - const contents = arrayBuffers.map(function (arrayBuffer, i) { - if (!defined(arrayBuffer)) { - // Content was not fetched. The error was handled in - // the fetch promise. Return undefined to indicate partial failure. - return undefined; - } + try { + const contents = await createInnerContent( + multipleContents, + arrayBuffer, + i + ); + return contents; + } catch (error) { + handleInnerContentFailed(multipleContents, i, error); + } + }; - return createInnerContent(multipleContents, arrayBuffer, i); - }); + const promises = []; + for (let i = 0; i < arrayBuffers.length; ++i) { + const arrayBuffer = arrayBuffers[i]; + promises.push(createPromise(arrayBuffer, i)); + } - // Even if we had a partial success, mark that we finished creating - // contents - multipleContents._contentsCreated = true; - multipleContents._contents = contents.filter(defined); - }); + // Even if we had a partial success, mark that we finished creating + // contents + const contents = await Promise.all(promises); + multipleContents._contentsCreated = true; + multipleContents._contents = contents.filter(defined); + return contents; } -function createInnerContent(multipleContents, arrayBuffer, index) { +async function createInnerContent(multipleContents, arrayBuffer, index) { const preprocessed = preprocess3DTileContent(arrayBuffer); if (preprocessed.contentType === Cesium3DTileContentType.EXTERNAL_TILESET) { - const error = new RuntimeError( + throw new RuntimeError( "External tilesets are disallowed inside multiple contents" ); - - return handleInnerContentFailed(multipleContents, index, error); } multipleContents._disableSkipLevelOfDetail = @@ -555,16 +539,20 @@ function createInnerContent(multipleContents, arrayBuffer, index) { let content; const contentFactory = Cesium3DTileContentFactory[preprocessed.contentType]; if (defined(preprocessed.binaryPayload)) { - content = contentFactory( - tileset, - tile, - resource, - preprocessed.binaryPayload.buffer, - 0 + content = await Promise.resolve( + contentFactory( + tileset, + tile, + resource, + preprocessed.binaryPayload.buffer, + 0 + ) ); } else { // JSON formats - content = contentFactory(tileset, tile, resource, preprocessed.jsonPayload); + content = await Promise.resolve( + contentFactory(tileset, tile, resource, preprocessed.jsonPayload) + ); } const contentHeader = multipleContents._innerContentHeaders[index]; @@ -653,9 +641,13 @@ Multiple3DTileContent.prototype.applyStyle = function (style) { Multiple3DTileContent.prototype.update = function (tileset, frameState) { const contents = this._contents; const length = contents.length; + let ready = true; for (let i = 0; i < length; ++i) { contents[i].update(tileset, frameState); + ready = ready && contents[i].ready; } + + this._ready = ready; }; Multiple3DTileContent.prototype.isDestroyed = function () { diff --git a/packages/engine/Source/Scene/ResourceCache.js b/packages/engine/Source/Scene/ResourceCache.js index 1aaaedbd05bf..21d8e8c4a664 100644 --- a/packages/engine/Source/Scene/ResourceCache.js +++ b/packages/engine/Source/Scene/ResourceCache.js @@ -532,19 +532,11 @@ function hasDracoCompression(draco, semantic) { * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * @param {FrameState} options.frameState The frame state. -<<<<<<< HEAD - * @param {Object} [options.draco] The Draco extension object. - * @param {Boolean} [options.asynchronous=true] Determines if WebGL resource creation will be spread out over several frames or block until all WebGL resources are created. - * @param {Boolean} [options.loadBuffer=false] Load index buffer as a GPU index buffer. - * @param {Boolean} [options.loadTypedArray=false] Load index buffer as a typed array. - * @returns {GltfIndexBufferLoader} The cached index buffer loader. -======= * @param {object} [options.draco] The Draco extension object. * @param {boolean} [options.asynchronous=true] Determines if WebGL resource creation will be spread out over several frames or block until all WebGL resources are created. * @param {boolean} [options.loadBuffer=false] Load index buffer as a GPU index buffer. * @param {boolean} [options.loadTypedArray=false] Load index buffer as a typed array. - * @returns {GltfIndexBufferLoader} The index buffer loader. ->>>>>>> no-ready-promises + * @returns {GltfIndexBufferLoader} The cached index buffer loader. * @private */ ResourceCache.getIndexBufferLoader = function (options) { diff --git a/packages/engine/Source/Scene/ResourceCacheStatistics.js b/packages/engine/Source/Scene/ResourceCacheStatistics.js index 08aaf513dadf..3492d757be18 100644 --- a/packages/engine/Source/Scene/ResourceCacheStatistics.js +++ b/packages/engine/Source/Scene/ResourceCacheStatistics.js @@ -55,7 +55,6 @@ ResourceCacheStatistics.prototype.clear = function () { *
  • If the geometry has a CPU copy of the GPU buffer, it will be added to the count
  • * * @param {GltfVertexBufferLoader|GltfIndexBufferLoader} loader The geometry buffer with resources to track - * @returns {Promise} A promise that resolves once the count is updated. * * @private */ @@ -99,7 +98,7 @@ ResourceCacheStatistics.prototype.addGeometryLoader = function (loader) { * * @private */ -ResourceCacheStatistics.prototype.addTextureLoader = async function (loader) { +ResourceCacheStatistics.prototype.addTextureLoader = function (loader) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("loader", loader); //>>includeEnd('debug'); diff --git a/packages/engine/Source/Scene/Tileset3DTileContent.js b/packages/engine/Source/Scene/Tileset3DTileContent.js index 37780112b4a6..e3ea176125b1 100644 --- a/packages/engine/Source/Scene/Tileset3DTileContent.js +++ b/packages/engine/Source/Scene/Tileset3DTileContent.js @@ -13,7 +13,7 @@ import destroyObject from "../Core/destroyObject.js"; * * @private */ -function Tileset3DTileContent(tileset, tile, resource, json) { +function Tileset3DTileContent(tileset, tile, resource) { this._tileset = tileset; this._tile = tile; this._resource = resource; @@ -23,7 +23,7 @@ function Tileset3DTileContent(tileset, tile, resource, json) { this._metadata = undefined; this._group = undefined; - this._readyPromise = initialize(this, json); + this._ready = false; } Object.defineProperties(Tileset3DTileContent.prototype, { @@ -69,9 +69,9 @@ Object.defineProperties(Tileset3DTileContent.prototype, { }, }, - readyPromise: { + ready: { get: function () { - return this._readyPromise; + return this._ready; }, }, @@ -118,10 +118,20 @@ Object.defineProperties(Tileset3DTileContent.prototype, { }, }); -function initialize(content, json) { +/** + * + * @param {Cesium3DTileset} tileset + * @param {Cesium3DTile} tile + * @param {Resource} resource + * @param {object} json + * @returns + */ +Tileset3DTileContent.fromJson = function (tileset, tile, resource, json) { + const content = new Tileset3DTileContent(tileset, tile, resource); content._tileset.loadTileset(content._resource, json, content._tile); - return Promise.resolve(content); -} + content._ready = true; + return content; +}; /** * Part of the {@link Cesium3DTileContent} interface. Tileset3DTileContent diff --git a/packages/engine/Source/Scene/Vector3DTileClampedPolylines.js b/packages/engine/Source/Scene/Vector3DTileClampedPolylines.js index 143b934892b8..657599492973 100644 --- a/packages/engine/Source/Scene/Vector3DTileClampedPolylines.js +++ b/packages/engine/Source/Scene/Vector3DTileClampedPolylines.js @@ -112,9 +112,8 @@ function Vector3DTileClampedPolylines(options) { this._geometryByteLength = 0; this._ready = false; - this._update = function (polylines, frameState) {}; - this._readyPromise = initialize(this); - this._verticesPromise = undefined; + this._promise = undefined; + this._error = undefined; } Object.defineProperties(Vector3DTileClampedPolylines.prototype, { @@ -147,14 +146,14 @@ Object.defineProperties(Vector3DTileClampedPolylines.prototype, { }, /** - * Gets a promise that resolves when the primitive is ready to render. + * Returns true when the primitive is ready to render. * @memberof Vector3DTileClampedPolylines.prototype - * @type {Promise} + * @type {boolean} * @readonly */ - readyPromise: { + ready: { get: function () { - return this._readyPromise; + return this._ready; }, }, }); @@ -222,51 +221,55 @@ function createVertexArray(polylines, context) { return; } - if (!defined(polylines._verticesPromise)) { - let positions = polylines._positions; - let widths = polylines._widths; - let counts = polylines._counts; - let batchIds = polylines._transferrableBatchIds; + let positions = polylines._positions; + let widths = polylines._widths; + let counts = polylines._counts; + let batchIds = polylines._transferrableBatchIds; - let packedBuffer = polylines._packedBuffer; + let packedBuffer = polylines._packedBuffer; - if (!defined(packedBuffer)) { - // Copy because they may be the views on the same buffer. - positions = polylines._positions = positions.slice(); - widths = polylines._widths = widths.slice(); - counts = polylines._counts = counts.slice(); + if (!defined(packedBuffer)) { + // Copy because they may be the views on the same buffer. + positions = polylines._positions = positions.slice(); + widths = polylines._widths = widths.slice(); + counts = polylines._counts = counts.slice(); - batchIds = polylines._transferrableBatchIds = polylines._batchIds.slice(); + batchIds = polylines._transferrableBatchIds = polylines._batchIds.slice(); - packedBuffer = polylines._packedBuffer = packBuffer(polylines); - } + packedBuffer = polylines._packedBuffer = packBuffer(polylines); + } - const transferrableObjects = [ - positions.buffer, - widths.buffer, - counts.buffer, - batchIds.buffer, - packedBuffer.buffer, - ]; - const parameters = { - positions: positions.buffer, - widths: widths.buffer, - counts: counts.buffer, - batchIds: batchIds.buffer, - packedBuffer: packedBuffer.buffer, - keepDecodedPositions: polylines._keepDecodedPositions, - }; - - const verticesPromise = (polylines._verticesPromise = createVerticesTaskProcessor.scheduleTask( - parameters, - transferrableObjects - )); - if (!defined(verticesPromise)) { - // Postponed - return; - } + const transferrableObjects = [ + positions.buffer, + widths.buffer, + counts.buffer, + batchIds.buffer, + packedBuffer.buffer, + ]; + const parameters = { + positions: positions.buffer, + widths: widths.buffer, + counts: counts.buffer, + batchIds: batchIds.buffer, + packedBuffer: packedBuffer.buffer, + keepDecodedPositions: polylines._keepDecodedPositions, + }; + + const verticesPromise = createVerticesTaskProcessor.scheduleTask( + parameters, + transferrableObjects + ); + if (!defined(verticesPromise)) { + // Postponed + return; + } + + return verticesPromise + .then(function (result) { + if (polylines.isDestroyed()) { + return; + } - return verticesPromise.then(function (result) { if (polylines._keepDecodedPositions) { polylines._decodedPositions = new Float64Array(result.decodedPositions); polylines._decodedPositionOffsets = new Uint32Array( @@ -300,13 +303,21 @@ function createVertexArray(polylines, context) { ? new Uint16Array(result.indices) : new Uint32Array(result.indices); + finishVertexArray(polylines, context); polylines._ready = true; + }) + .catch((error) => { + if (polylines.isDestroyed()) { + return; + } + + // Throw the error next frame + polylines._error = error; }); - } } function finishVertexArray(polylines, context) { - if (polylines._ready && !defined(polylines._va)) { + if (!defined(polylines._va)) { const startEllipsoidNormals = polylines._startEllipsoidNormals; const endEllipsoidNormals = polylines._endEllipsoidNormals; const startPositionAndHeights = polylines._startPositionAndHeights; @@ -692,43 +703,22 @@ Vector3DTileClampedPolylines.prototype.applyStyle = function (style, features) { }; function initialize(polylines) { - return ApproximateTerrainHeights.initialize().then(function () { - updateMinimumMaximumHeights( - polylines, - polylines._rectangle, - polylines._ellipsoid - ); + return ApproximateTerrainHeights.initialize() + .then(function () { + updateMinimumMaximumHeights( + polylines, + polylines._rectangle, + polylines._ellipsoid + ); + }) + .catch((error) => { + if (polylines.isDestroyed()) { + return; + } - return new Promise(function (resolve, reject) { - polylines._update = function (polylines, frameState) { - const context = frameState.context; - const promise = createVertexArray(polylines, context); - createUniformMap(polylines, context); - createShaders(polylines, context); - createRenderStates(polylines); - - if (polylines._ready) { - const passes = frameState.passes; - if (passes.render || passes.pick) { - queueCommands(polylines, frameState); - } - } - - if (!defined(promise)) { - return; - } - - promise - .then(function () { - finishVertexArray(polylines, context); - resolve(polylines); - }) - .catch(function (e) { - reject(e); - }); - }; + // Throw the error next frame + polylines._error = error; }); - }); } /** @@ -737,7 +727,29 @@ function initialize(polylines) { * @param {FrameState} frameState The current frame state. */ Vector3DTileClampedPolylines.prototype.update = function (frameState) { - this._update(this, frameState); + const context = frameState.context; + if (!this._ready) { + if (!defined(this._promise)) { + this._promise = initialize(this).then(createVertexArray(this, context)); + } + + if (defined(this._error)) { + const error = this._error; + this._error = undefined; + throw error; + } + + return; + } + + createUniformMap(this, context); + createShaders(this, context); + createRenderStates(this); + + const passes = frameState.passes; + if (passes.render || passes.pick) { + queueCommands(this, frameState); + } }; /** diff --git a/packages/engine/Source/Scene/Vector3DTileContent.js b/packages/engine/Source/Scene/Vector3DTileContent.js index b2f970403bd6..56d7e750b5f0 100644 --- a/packages/engine/Source/Scene/Vector3DTileContent.js +++ b/packages/engine/Source/Scene/Vector3DTileContent.js @@ -25,6 +25,7 @@ import decodeVectorPolylinePositions from "../Core/decodeVectorPolylinePositions *

    * Implements the {@link Cesium3DTileContent} interface. *

    + * This object is normally not instantiated directly, use {@link Vector3DTileContent.fromTile}. * * @alias Vector3DTileContent * @constructor @@ -51,6 +52,7 @@ function Vector3DTileContent(tileset, tile, resource, arrayBuffer, byteOffset) { this.featurePropertiesDirty = false; this._group = undefined; + this._ready = false; initialize(this, arrayBuffer, byteOffset); } @@ -119,24 +121,9 @@ Object.defineProperties(Vector3DTileContent.prototype, { }, }, - readyPromise: { + ready: { get: function () { - const pointsPromise = defined(this._points) - ? this._points.readyPromise - : undefined; - const polygonPromise = defined(this._polygons) - ? this._polygons.readyPromise - : undefined; - const polylinePromise = defined(this._polylines) - ? this._polylines.readyPromise - : undefined; - - const that = this; - return Promise.all([pointsPromise, polygonPromise, polylinePromise]).then( - function () { - return that; - } - ); + return this._ready; }, }, @@ -245,7 +232,7 @@ function getBatchIds(featureTableJson, featureTableBinary) { if (atLeastOneDefined && atLeastOneUndefined) { throw new RuntimeError( - "If one group of batch ids is defined, then all batch ids must be defined." + "If one group of batch ids is defined, then all batch ids must be defined" ); } @@ -311,7 +298,7 @@ function initialize(content, arrayBuffer, byteOffset) { byteOffset += sizeOfUint32; if (byteLength === 0) { - return Promise.resolve(content); + return; } const featureTableJSONByteLength = view.getUint32(byteOffset, true); @@ -636,7 +623,7 @@ function initialize(content, arrayBuffer, byteOffset) { }); } - return Promise.resolve(content); + return; } function createFeatures(content) { @@ -713,21 +700,22 @@ Vector3DTileContent.prototype.update = function (tileset, frameState) { this._polygons.classificationType = this._tileset.classificationType; this._polygons.debugWireframe = this._tileset.debugWireframe; this._polygons.update(frameState); - ready = ready && this._polygons._ready; + ready = ready && this._polygons.ready; } if (defined(this._polylines)) { this._polylines.update(frameState); - ready = ready && this._polylines._ready; + ready = ready && this._polylines.ready; } if (defined(this._points)) { this._points.update(frameState); - ready = ready && this._points._ready; + ready = ready && this._points.ready; } if (defined(this._batchTable) && ready) { if (!defined(this._features)) { createFeatures(this); } this._batchTable.update(tileset, frameState); + this._ready = true; } }; diff --git a/packages/engine/Source/Scene/Vector3DTileGeometry.js b/packages/engine/Source/Scene/Vector3DTileGeometry.js index 4e4c9be5da89..9dd4c3e71389 100644 --- a/packages/engine/Source/Scene/Vector3DTileGeometry.js +++ b/packages/engine/Source/Scene/Vector3DTileGeometry.js @@ -71,8 +71,8 @@ function Vector3DTileGeometry(options) { this._packedBuffer = undefined; this._ready = false; - this._update = function (geometries, frameState) {}; - this._readyPromise = initialize(this); + this._promise = undefined; + this._error = undefined; this._verticesPromise = undefined; @@ -136,14 +136,14 @@ Object.defineProperties(Vector3DTileGeometry.prototype, { }, /** - * Gets a promise that resolves when the primitive is ready to render. + * Return true when the primitive is ready to render. * @memberof Vector3DTileGeometry.prototype - * @type {Promise} + * @type {boolean} * @readonly */ - readyPromise: { + ready: { get: function () { - return this._readyPromise; + return this._ready; }, }, }); @@ -308,31 +308,45 @@ function createPrimitive(geometries) { return; } - return verticesPromise.then(function (result) { - const packedBuffer = new Float64Array(result.packedBuffer); - const indicesBytesPerElement = unpackBuffer(geometries, packedBuffer); + return verticesPromise + .then(function (result) { + if (geometries.isDestroyed()) { + return; + } - if (indicesBytesPerElement === 2) { - geometries._indices = new Uint16Array(result.indices); - } else { - geometries._indices = new Uint32Array(result.indices); - } + const packedBuffer = new Float64Array(result.packedBuffer); + const indicesBytesPerElement = unpackBuffer(geometries, packedBuffer); - geometries._indexOffsets = new Uint32Array(result.indexOffsets); - geometries._indexCounts = new Uint32Array(result.indexCounts); + if (indicesBytesPerElement === 2) { + geometries._indices = new Uint16Array(result.indices); + } else { + geometries._indices = new Uint32Array(result.indices); + } - geometries._positions = new Float32Array(result.positions); - geometries._vertexBatchIds = new Uint16Array(result.vertexBatchIds); + geometries._indexOffsets = new Uint32Array(result.indexOffsets); + geometries._indexCounts = new Uint32Array(result.indexCounts); - geometries._batchIds = new Uint16Array(result.batchIds); + geometries._positions = new Float32Array(result.positions); + geometries._vertexBatchIds = new Uint16Array(result.vertexBatchIds); - geometries._ready = true; - }); + geometries._batchIds = new Uint16Array(result.batchIds); + + finishPrimitive(geometries); + + geometries._ready = true; + }) + .catch((error) => { + if (geometries.isDestroyed()) { + return; + } + + this._error = error; + }); } } function finishPrimitive(geometries) { - if (geometries._ready && !defined(geometries._primitive)) { + if (!defined(geometries._primitive)) { geometries._primitive = new Vector3DTilePrimitive({ batchTable: geometries._batchTable, positions: geometries._positions, @@ -421,42 +435,30 @@ Vector3DTileGeometry.prototype.updateCommands = function (batchId, color) { this._primitive.updateCommands(batchId, color); }; -function initialize(geometries) { - return new Promise(function (resolve, reject) { - geometries._update = function (geometries, frameState) { - const promise = createPrimitive(geometries); - - if (geometries._ready) { - geometries._primitive.debugWireframe = geometries.debugWireframe; - geometries._primitive.forceRebatch = geometries.forceRebatch; - geometries._primitive.classificationType = - geometries.classificationType; - geometries._primitive.update(frameState); - } - - if (!defined(promise)) { - return; - } - - promise - .then(function () { - finishPrimitive(geometries); - resolve(geometries); - }) - .catch(function (e) { - reject(e); - }); - }; - }); -} - /** * Updates the batches and queues the commands for rendering. * * @param {FrameState} frameState The current frame state. */ Vector3DTileGeometry.prototype.update = function (frameState) { - this._update(this, frameState); + if (!this._ready) { + if (!defined(this._promise)) { + this._promise = createPrimitive(this); + } + + if (defined(this._error)) { + const error = this._error; + this._error = undefined; + throw error; + } + + return; + } + + this._primitive.debugWireframe = this.debugWireframe; + this._primitive.forceRebatch = this.forceRebatch; + this._primitive.classificationType = this.classificationType; + this._primitive.update(frameState); }; /** diff --git a/packages/engine/Source/Scene/Vector3DTilePoints.js b/packages/engine/Source/Scene/Vector3DTilePoints.js index d083bc81625e..76e151edc6f6 100644 --- a/packages/engine/Source/Scene/Vector3DTilePoints.js +++ b/packages/engine/Source/Scene/Vector3DTilePoints.js @@ -56,11 +56,25 @@ function Vector3DTilePoints(options) { this._packedBuffer = undefined; this._ready = false; - this._update = function (points, frameState) {}; - this._readyPromise = initialize(this); + this._promise = undefined; + this._error = undefined; } Object.defineProperties(Vector3DTilePoints.prototype, { + /** + * Returns true if the points are ready to render + * + * @memberof Vector3DTilePoints.prototype + * + * @type {boolean} + * @readonly + */ + ready: { + get: function () { + return this._ready; + }, + }, + /** * Gets the number of points. * @@ -91,18 +105,6 @@ Object.defineProperties(Vector3DTilePoints.prototype, { return billboardSize + labelSize; }, }, - - /** - * Gets a promise that resolves when the primitive is ready to render. - * @memberof Vector3DTilePoints.prototype - * @type {Promise} - * @readonly - */ - readyPromise: { - get: function () { - return this._readyPromise; - }, - }, }); function packBuffer(points, ellipsoid) { @@ -132,35 +134,38 @@ const createVerticesTaskProcessor = new TaskProcessor( const scratchPosition = new Cartesian3(); function createPoints(points, ellipsoid) { - let positions; - if (!defined(points._verticesPromise)) { - positions = points._positions; - let packedBuffer = points._packedBuffer; + let positions = points._positions; + let packedBuffer = points._packedBuffer; - if (!defined(packedBuffer)) { - // Copy because they may be the views on the same buffer. - positions = points._positions = positions.slice(); - points._batchIds = points._batchIds.slice(); + if (!defined(packedBuffer)) { + // Copy because they may be the views on the same buffer. + positions = points._positions = positions.slice(); + points._batchIds = points._batchIds.slice(); - packedBuffer = points._packedBuffer = packBuffer(points, ellipsoid); - } + packedBuffer = points._packedBuffer = packBuffer(points, ellipsoid); + } - const transferrableObjects = [positions.buffer, packedBuffer.buffer]; - const parameters = { - positions: positions.buffer, - packedBuffer: packedBuffer.buffer, - }; - - const verticesPromise = (points._verticesPromise = createVerticesTaskProcessor.scheduleTask( - parameters, - transferrableObjects - )); - if (!defined(verticesPromise)) { - // Postponed - return; - } + const transferrableObjects = [positions.buffer, packedBuffer.buffer]; + const parameters = { + positions: positions.buffer, + packedBuffer: packedBuffer.buffer, + }; + + const verticesPromise = createVerticesTaskProcessor.scheduleTask( + parameters, + transferrableObjects + ); + if (!defined(verticesPromise)) { + // Postponed + return; + } + + return verticesPromise + .then((result) => { + if (points.isDestroyed()) { + return; + } - return verticesPromise.then(function (result) { points._positions = new Float64Array(result.positions); const billboardCollection = points._billboardCollection; const labelCollection = points._labelCollection; @@ -190,8 +195,15 @@ function createPoints(points, ellipsoid) { points._positions = undefined; points._packedBuffer = undefined; points._ready = true; + }) + .catch((error) => { + if (points.isDestroyed()) { + return; + } + + // Throw the error next frame + points._error = error; }); - } } /** @@ -460,37 +472,27 @@ Vector3DTilePoints.prototype.applyStyle = function (style, features) { } }; -function initialize(points) { - return new Promise(function (resolve, reject) { - points._update = function (points, frameState) { - const promise = createPoints(points, frameState.mapProjection.ellipsoid); - - if (points._ready) { - points._polylineCollection.update(frameState); - points._billboardCollection.update(frameState); - points._labelCollection.update(frameState); - } - - if (!defined(promise)) { - return; - } - - promise - .then(function () { - resolve(); - }) - .catch(function (e) { - reject(e); - }); - }; - }); -} - /** * @private */ Vector3DTilePoints.prototype.update = function (frameState) { - this._update(this, frameState); + if (!this._ready) { + if (!defined(this._promise)) { + this._promise = createPoints(this, frameState.mapProjection.ellipsoid); + } + + if (defined(this._error)) { + const error = this._error; + this._error = undefined; + throw error; + } + + return; + } + + this._polylineCollection.update(frameState); + this._billboardCollection.update(frameState); + this._labelCollection.update(frameState); }; /** diff --git a/packages/engine/Source/Scene/Vector3DTilePolygons.js b/packages/engine/Source/Scene/Vector3DTilePolygons.js index 835ae4cd1866..a5a37be0fc7e 100644 --- a/packages/engine/Source/Scene/Vector3DTilePolygons.js +++ b/packages/engine/Source/Scene/Vector3DTilePolygons.js @@ -75,10 +75,8 @@ function Vector3DTilePolygons(options) { this._batchedIndices = undefined; this._ready = false; - this._update = function (polygons, frameState) {}; - this._readyPromise = initialize(this); - this._verticesPromise = undefined; - + this._promise = undefined; + this._error = undefined; this._primitive = undefined; /** @@ -139,14 +137,14 @@ Object.defineProperties(Vector3DTilePolygons.prototype, { }, /** - * Gets a promise that resolves when the primitive is ready to render. + * Returns true when the primitive is ready to render. * @memberof Vector3DTilePolygons.prototype - * @type {Promise} + * @type {boolean} * @readonly */ - readyPromise: { + ready: { get: function () { - return this._readyPromise; + return this._ready; }, }, }); @@ -224,85 +222,89 @@ function createPrimitive(polygons) { return; } - if (!defined(polygons._verticesPromise)) { - let positions = polygons._positions; - let counts = polygons._counts; - let indexCounts = polygons._indexCounts; - let indices = polygons._indices; - - let batchIds = polygons._transferrableBatchIds; - let batchTableColors = polygons._batchTableColors; - - let packedBuffer = polygons._packedBuffer; - - if (!defined(batchTableColors)) { - // Copy because they may be the views on the same buffer. - positions = polygons._positions = polygons._positions.slice(); - counts = polygons._counts = polygons._counts.slice(); - indexCounts = polygons._indexCounts = polygons._indexCounts.slice(); - indices = polygons._indices = polygons._indices.slice(); - - polygons._center = polygons._ellipsoid.cartographicToCartesian( - Rectangle.center(polygons._rectangle) - ); - - batchIds = polygons._transferrableBatchIds = new Uint32Array( - polygons._batchIds - ); - batchTableColors = polygons._batchTableColors = new Uint32Array( - batchIds.length - ); - const batchTable = polygons._batchTable; - - const length = batchTableColors.length; - for (let i = 0; i < length; ++i) { - const color = batchTable.getColor(i, scratchColor); - batchTableColors[i] = color.toRgba(); - } - - packedBuffer = polygons._packedBuffer = packBuffer(polygons); + let positions = polygons._positions; + let counts = polygons._counts; + let indexCounts = polygons._indexCounts; + let indices = polygons._indices; + + let batchIds = polygons._transferrableBatchIds; + let batchTableColors = polygons._batchTableColors; + + let packedBuffer = polygons._packedBuffer; + + if (!defined(batchTableColors)) { + // Copy because they may be the views on the same buffer. + positions = polygons._positions = polygons._positions.slice(); + counts = polygons._counts = polygons._counts.slice(); + indexCounts = polygons._indexCounts = polygons._indexCounts.slice(); + indices = polygons._indices = polygons._indices.slice(); + + polygons._center = polygons._ellipsoid.cartographicToCartesian( + Rectangle.center(polygons._rectangle) + ); + + batchIds = polygons._transferrableBatchIds = new Uint32Array( + polygons._batchIds + ); + batchTableColors = polygons._batchTableColors = new Uint32Array( + batchIds.length + ); + const batchTable = polygons._batchTable; + + const length = batchTableColors.length; + for (let i = 0; i < length; ++i) { + const color = batchTable.getColor(i, scratchColor); + batchTableColors[i] = color.toRgba(); } - const transferrableObjects = [ - positions.buffer, - counts.buffer, - indexCounts.buffer, - indices.buffer, - batchIds.buffer, - batchTableColors.buffer, - packedBuffer.buffer, - ]; - const parameters = { - packedBuffer: packedBuffer.buffer, - positions: positions.buffer, - counts: counts.buffer, - indexCounts: indexCounts.buffer, - indices: indices.buffer, - batchIds: batchIds.buffer, - batchTableColors: batchTableColors.buffer, - }; - - let minimumHeights = polygons._polygonMinimumHeights; - let maximumHeights = polygons._polygonMaximumHeights; - if (defined(minimumHeights) && defined(maximumHeights)) { - minimumHeights = minimumHeights.slice(); - maximumHeights = maximumHeights.slice(); - - transferrableObjects.push(minimumHeights.buffer, maximumHeights.buffer); - parameters.minimumHeights = minimumHeights; - parameters.maximumHeights = maximumHeights; - } + packedBuffer = polygons._packedBuffer = packBuffer(polygons); + } - const verticesPromise = (polygons._verticesPromise = createVerticesTaskProcessor.scheduleTask( - parameters, - transferrableObjects - )); - if (!defined(verticesPromise)) { - // Postponed - return; - } + const transferrableObjects = [ + positions.buffer, + counts.buffer, + indexCounts.buffer, + indices.buffer, + batchIds.buffer, + batchTableColors.buffer, + packedBuffer.buffer, + ]; + const parameters = { + packedBuffer: packedBuffer.buffer, + positions: positions.buffer, + counts: counts.buffer, + indexCounts: indexCounts.buffer, + indices: indices.buffer, + batchIds: batchIds.buffer, + batchTableColors: batchTableColors.buffer, + }; + + let minimumHeights = polygons._polygonMinimumHeights; + let maximumHeights = polygons._polygonMaximumHeights; + if (defined(minimumHeights) && defined(maximumHeights)) { + minimumHeights = minimumHeights.slice(); + maximumHeights = maximumHeights.slice(); + + transferrableObjects.push(minimumHeights.buffer, maximumHeights.buffer); + parameters.minimumHeights = minimumHeights; + parameters.maximumHeights = maximumHeights; + } + + const verticesPromise = createVerticesTaskProcessor.scheduleTask( + parameters, + transferrableObjects + ); + if (!defined(verticesPromise)) { + // Postponed + return; + } + + return verticesPromise + .then((result) => { + if (polygons.isDestroyed()) { + return; + } - return verticesPromise.then(function (result) { polygons._positions = undefined; polygons._counts = undefined; polygons._polygonMinimumHeights = undefined; @@ -323,13 +325,22 @@ function createPrimitive(polygons) { polygons._batchedPositions = new Float32Array(result.positions); polygons._vertexBatchIds = new Uint16Array(result.batchIds); + finishPrimitive(polygons); + polygons._ready = true; + }) + .catch((error) => { + if (polygons.isDestroyed()) { + return; + } + + // Throw the error next frame + polygons._error = error; }); - } } function finishPrimitive(polygons) { - if (polygons._ready && !defined(polygons._primitive)) { + if (!defined(polygons._primitive)) { polygons._primitive = new Vector3DTilePrimitive({ batchTable: polygons._batchTable, positions: polygons._batchedPositions, @@ -411,41 +422,30 @@ Vector3DTilePolygons.prototype.updateCommands = function (batchId, color) { this._primitive.updateCommands(batchId, color); }; -function initialize(polygons) { - return new Promise(function (resolve, reject) { - polygons._update = function (polygons, frameState) { - const promise = createPrimitive(polygons); - - if (polygons._ready) { - polygons._primitive.debugWireframe = polygons.debugWireframe; - polygons._primitive.forceRebatch = polygons.forceRebatch; - polygons._primitive.classificationType = polygons.classificationType; - polygons._primitive.update(frameState); - } - - if (!defined(promise)) { - return; - } - - promise - .then(function () { - finishPrimitive(polygons); - resolve(polygons); - }) - .catch(function (e) { - reject(e); - }); - }; - }); -} - /** * Updates the batches and queues the commands for rendering. * * @param {FrameState} frameState The current frame state. */ Vector3DTilePolygons.prototype.update = function (frameState) { - this._update(this, frameState); + if (!this._ready) { + if (!defined(this._promise)) { + this._promise = createPrimitive(this); + } + + if (defined(this._error)) { + const error = this._error; + this._error = undefined; + throw error; + } + + return; + } + + this._primitive.debugWireframe = this.debugWireframe; + this._primitive.forceRebatch = this.forceRebatch; + this._primitive.classificationType = this.classificationType; + this._primitive.update(frameState); }; /** diff --git a/packages/engine/Source/Scene/Vector3DTilePolylines.js b/packages/engine/Source/Scene/Vector3DTilePolylines.js index e9dd0268fbcb..70baacb6d69c 100644 --- a/packages/engine/Source/Scene/Vector3DTilePolylines.js +++ b/packages/engine/Source/Scene/Vector3DTilePolylines.js @@ -87,10 +87,8 @@ function Vector3DTilePolylines(options) { this._geometryByteLength = 0; this._ready = false; - this._update = function (polylines, frameState) {}; - this._readyPromise = initialize(this); - - this._verticesPromise = undefined; + this._promise = undefined; + this._error = undefined; } Object.defineProperties(Vector3DTilePolylines.prototype, { @@ -123,14 +121,14 @@ Object.defineProperties(Vector3DTilePolylines.prototype, { }, /** - * Gets a promise that resolves when the primitive is ready to render. + * Returns true when the primitive is ready to render. * @memberof Vector3DTilePolylines.prototype - * @type {Promise} + * @type {boolean} * @readonly */ - readyPromise: { + ready: { get: function () { - return this._readyPromise; + return this._ready; }, }, }); @@ -181,51 +179,55 @@ function createVertexArray(polylines, context) { return; } - if (!defined(polylines._verticesPromise)) { - let positions = polylines._positions; - let widths = polylines._widths; - let counts = polylines._counts; - let batchIds = polylines._transferrableBatchIds; + let positions = polylines._positions; + let widths = polylines._widths; + let counts = polylines._counts; + let batchIds = polylines._transferrableBatchIds; - let packedBuffer = polylines._packedBuffer; + let packedBuffer = polylines._packedBuffer; - if (!defined(packedBuffer)) { - // Copy because they may be the views on the same buffer. - positions = polylines._positions = positions.slice(); - widths = polylines._widths = widths.slice(); - counts = polylines._counts = counts.slice(); + if (!defined(packedBuffer)) { + // Copy because they may be the views on the same buffer. + positions = polylines._positions = positions.slice(); + widths = polylines._widths = widths.slice(); + counts = polylines._counts = counts.slice(); - batchIds = polylines._transferrableBatchIds = polylines._batchIds.slice(); + batchIds = polylines._transferrableBatchIds = polylines._batchIds.slice(); - packedBuffer = polylines._packedBuffer = packBuffer(polylines); - } + packedBuffer = polylines._packedBuffer = packBuffer(polylines); + } - const transferrableObjects = [ - positions.buffer, - widths.buffer, - counts.buffer, - batchIds.buffer, - packedBuffer.buffer, - ]; - const parameters = { - positions: positions.buffer, - widths: widths.buffer, - counts: counts.buffer, - batchIds: batchIds.buffer, - packedBuffer: packedBuffer.buffer, - keepDecodedPositions: polylines._keepDecodedPositions, - }; - - const verticesPromise = (polylines._verticesPromise = createVerticesTaskProcessor.scheduleTask( - parameters, - transferrableObjects - )); - if (!defined(verticesPromise)) { - // Postponed - return; - } + const transferrableObjects = [ + positions.buffer, + widths.buffer, + counts.buffer, + batchIds.buffer, + packedBuffer.buffer, + ]; + const parameters = { + positions: positions.buffer, + widths: widths.buffer, + counts: counts.buffer, + batchIds: batchIds.buffer, + packedBuffer: packedBuffer.buffer, + keepDecodedPositions: polylines._keepDecodedPositions, + }; + + const verticesPromise = createVerticesTaskProcessor.scheduleTask( + parameters, + transferrableObjects + ); + if (!defined(verticesPromise)) { + // Postponed + return; + } + + return verticesPromise + .then(function (result) { + if (polylines.isDestroyed()) { + return; + } - return verticesPromise.then(function (result) { if (polylines._keepDecodedPositions) { polylines._decodedPositions = new Float64Array(result.decodedPositions); polylines._decodedPositionOffsets = new Uint32Array( @@ -245,13 +247,22 @@ function createVertexArray(polylines, context) { ? new Uint16Array(result.indices) : new Uint32Array(result.indices); + finishVertexArray(polylines, context); + polylines._ready = true; + }) + .catch((error) => { + if (polylines.isDestroyed()) { + return; + } + + // Throw the error next frame + polylines._error = error; }); - } } function finishVertexArray(polylines, context) { - if (polylines._ready && !defined(polylines._va)) { + if (!defined(polylines._va)) { const curPositions = polylines._currentPositions; const prevPositions = polylines._previousPositions; const nextPositions = polylines._nextPositions; @@ -603,45 +614,35 @@ Vector3DTilePolylines.prototype.applyStyle = function (style, features) { } }; -function initialize(polylines) { - return new Promise(function (resolve, reject) { - polylines._update = function (polylines, frameState) { - const context = frameState.context; - const promise = createVertexArray(polylines, context); - createUniformMap(polylines, context); - createShaders(polylines, context); - createRenderStates(polylines); - - if (polylines._ready) { - const passes = frameState.passes; - if (passes.render || passes.pick) { - queueCommands(polylines, frameState); - } - } - - if (!defined(promise)) { - return; - } - - promise - .then(function () { - finishVertexArray(polylines, context); - resolve(); - }) - .catch(function (e) { - reject(e); - }); - }; - }); -} - /** * Updates the batches and queues the commands for rendering. * * @param {FrameState} frameState The current frame state. */ Vector3DTilePolylines.prototype.update = function (frameState) { - this._update(this, frameState); + const context = frameState.context; + if (!this._ready) { + if (!defined(this._promise)) { + this._promise = createVertexArray(this, context); + } + + if (defined(this._error)) { + const error = this._error; + this._error = undefined; + throw error; + } + + return; + } + + createUniformMap(this, context); + createShaders(this, context); + createRenderStates(this); + + const passes = frameState.passes; + if (passes.render || passes.pick) { + queueCommands(this, frameState); + } }; /** diff --git a/packages/engine/Specs/DataSources/ModelVisualizerSpec.js b/packages/engine/Specs/DataSources/ModelVisualizerSpec.js index 3f3325dd3482..afd6cef84f30 100644 --- a/packages/engine/Specs/DataSources/ModelVisualizerSpec.js +++ b/packages/engine/Specs/DataSources/ModelVisualizerSpec.js @@ -24,7 +24,7 @@ import { CustomShader, Globe, Cartographic, - createWorldTerrain, + createWorldTerrainAsync, } from "../../index.js"; import createScene from "../../../../Specs/createScene.js"; import pollToPromise from "../../../../Specs/pollToPromise.js"; @@ -39,10 +39,12 @@ describe( let scene; let entityCollection; let visualizer; + let originalTerrainProvider; beforeAll(function () { scene = createScene(); scene.globe = new Globe(); + originalTerrainProvider = scene.globe.terrainProvider; }); beforeEach(function () { @@ -53,6 +55,7 @@ describe( afterEach(function () { visualizer = visualizer && visualizer.destroy(); entityCollection.removeAll(); + scene.globe.terrainProvider = originalTerrainProvider; }); afterAll(function () { @@ -193,7 +196,7 @@ describe( new Cartesian2(0.5, 0.5) ); - expect(primitive.lightColor).toEqual(new Color(1.0, 1.0, 0.0, 1.0)); + expect(primitive.lightColor).toEqual(new Cartesian3(1.0, 1.0, 0.0)); // wait till the model is loaded before we can check node transformations await pollToPromise(function () { @@ -392,6 +395,7 @@ describe( await pollToPromise(function () { scene.render(); + visualizer.update(time); state = visualizer.getBoundingSphere(testObject, result); return state !== BoundingSphereState.PENDING; }); @@ -403,17 +407,11 @@ describe( expect(result).toEqual(expected); }); - it("computes bounding sphere with height reference clamp to ground", function () { + it("computes bounding sphere with height reference clamp to ground", async function () { // Setup a position for the model. const position = Cartesian3.fromDegrees(149.515332, -34.984799); const positionCartographic = Cartographic.fromCartesian(position); - // Setup a spy so we can track how often sampleTerrain is called. - const sampleTerrainSpy = spyOn( - ModelVisualizer, - "_sampleTerrainMostDetailed" - ).and.callThrough(); - // Initialize the Entity and the ModelGraphics. const time = JulianDate.now(); const testObject = entityCollection.getOrCreateEntity("test"); @@ -433,49 +431,45 @@ describe( // Assign a tiled terrain provider to the globe. const globe = scene.globe; - const previousTerrainProvider = globe.terrainProvider; - globe.terrainProvider = createWorldTerrain(); - - let sampledResultCartographic; - let sampledResult; - - return ModelVisualizer._sampleTerrainMostDetailed(globe.terrainProvider, [ - positionCartographic, - ]) - .then((updatedCartographics) => { - sampledResultCartographic = updatedCartographics[0]; - sampledResult = globe.ellipsoid.cartographicToCartesian( - sampledResultCartographic - ); - - // Repeatedly request the bounding sphere until it's ready. - return pollToPromise(function () { - scene.render(); - state = visualizer.getBoundingSphere(testObject, result); - return state !== BoundingSphereState.PENDING; - }); - }) - .then(() => { - expect(state).toBe(BoundingSphereState.DONE); - - // Ensure that flags and results computed for this model are reset. - const modelData = visualizer._modelHash[testObject.id]; - expect(modelData.awaitingSampleTerrain).toBe(false); - expect(modelData.clampedBoundingSphere).toBeUndefined(); - - // Ensure that we only sample the terrain once from the visualizer. - // We check for 2 calls here because we call it once in the test. - expect(sampleTerrainSpy).toHaveBeenCalledTimes(2); - - // Calculate the distance of the bounding sphere returned from the position returned from sample terrain. - // Since sampleTerrainMostDetailed isn't always precise, we account for some error. - const distance = Cartesian3.distance(result.center, sampledResult); - const errorMargin = 100.0; - expect(distance).toBeLessThan(errorMargin); - - // Reset the terrain provider. - globe.terrainProvider = previousTerrainProvider; - }); + globe.terrainProvider = await createWorldTerrainAsync(); + + const updatedCartographics = await ModelVisualizer._sampleTerrainMostDetailed( + globe.terrainProvider, + [positionCartographic] + ); + const sampledResultCartographic = updatedCartographics[0]; + const sampledResult = globe.ellipsoid.cartographicToCartesian( + sampledResultCartographic + ); + + const sampleTerrainSpy = spyOn( + ModelVisualizer, + "_sampleTerrainMostDetailed" + ).and.callThrough(); + + // Repeatedly request the bounding sphere until it's ready. + await pollToPromise(function () { + scene.render(); + visualizer.update(time); + state = visualizer.getBoundingSphere(testObject, result); + return state !== BoundingSphereState.PENDING; + }); + + expect(state).toBe(BoundingSphereState.DONE); + + // Ensure that flags and results computed for this model are reset. + const modelData = visualizer._modelHash[testObject.id]; + expect(modelData.awaitingSampleTerrain).toBe(false); + expect(modelData.clampedBoundingSphere).toBeUndefined(); + + // Ensure that we only sample the terrain once from the visualizer. + expect(sampleTerrainSpy).toHaveBeenCalledTimes(1); + + // Calculate the distance of the bounding sphere returned from the position returned from sample terrain. + // Since sampleTerrainMostDetailed isn't always precise, we account for some error. + const distance = Cartesian3.distance(result.center, sampledResult); + const errorMargin = 100.0; + expect(distance).toBeLessThan(errorMargin); }); it("computes bounding sphere with height reference clamp to ground on terrain provider without availability", function () { @@ -510,6 +504,7 @@ describe( // Repeatedly request the bounding sphere until it's ready. return pollToPromise(function () { scene.render(); + visualizer.update(time); state = visualizer.getBoundingSphere(testObject, result); return state !== BoundingSphereState.PENDING; }).then(() => { @@ -530,7 +525,7 @@ describe( }); }); - it("computes bounding sphere with height reference relative to ground", function () { + it("computes bounding sphere with height reference relative to ground", async function () { // Setup a position for the model. const heightOffset = 1000.0; const position = Cartesian3.fromDegrees( @@ -565,50 +560,41 @@ describe( // Assign a tiled terrain provider to the globe. const globe = scene.globe; - const previousTerrainProvider = globe.terrainProvider; - globe.terrainProvider = createWorldTerrain(); - - let sampledResultCartographic; - let sampledResult; - - return ModelVisualizer._sampleTerrainMostDetailed(globe.terrainProvider, [ - positionCartographic, - ]) - .then((updatedCartographics) => { - sampledResultCartographic = updatedCartographics[0]; - sampledResult = globe.ellipsoid.cartographicToCartesian( - sampledResultCartographic - ); - - // Repeatedly request the bounding sphere until it's ready. - return pollToPromise(function () { - scene.render(); - state = visualizer.getBoundingSphere(testObject, result); - return state !== BoundingSphereState.PENDING; - }); - }) - .then(() => { - expect(state).toBe(BoundingSphereState.DONE); - - // Ensure that flags and results computed for this model are reset. - const modelData = visualizer._modelHash[testObject.id]; - expect(modelData.awaitingSampleTerrain).toBe(false); - expect(modelData.clampedBoundingSphere).toBeUndefined(); - - // Ensure that we only sample the terrain once from the visualizer. - // We check for 2 calls here because we call it once in the test. - expect(sampleTerrainSpy).toHaveBeenCalledTimes(2); - - // Calculate the distance of the bounding sphere returned from the position returned from sample terrain. - // Since sampleTerrainMostDetailed isn't always precise, we account for some error. - const distance = - Cartesian3.distance(result.center, sampledResult) - heightOffset; - const errorMargin = 100.0; - expect(distance).toBeLessThan(errorMargin); - - // Reset the terrain provider. - globe.terrainProvider = previousTerrainProvider; - }); + globe.terrainProvider = await createWorldTerrainAsync(); + + const updatedCartographics = await ModelVisualizer._sampleTerrainMostDetailed( + globe.terrainProvider, + [positionCartographic] + ); + const sampledResultCartographic = updatedCartographics[0]; + const sampledResult = globe.ellipsoid.cartographicToCartesian( + sampledResultCartographic + ); + + // Repeatedly request the bounding sphere until it's ready. + await pollToPromise(function () { + scene.render(); + visualizer.update(time); + state = visualizer.getBoundingSphere(testObject, result); + return state !== BoundingSphereState.PENDING; + }); + expect(state).toBe(BoundingSphereState.DONE); + + // Ensure that flags and results computed for this model are reset. + const modelData = visualizer._modelHash[testObject.id]; + expect(modelData.awaitingSampleTerrain).toBe(false); + expect(modelData.clampedBoundingSphere).toBeUndefined(); + + // Ensure that we only sample the terrain once from the visualizer. + // We check for 2 calls here because we call it once in the test. + expect(sampleTerrainSpy).toHaveBeenCalledTimes(2); + + // Calculate the distance of the bounding sphere returned from the position returned from sample terrain. + // Since sampleTerrainMostDetailed isn't always precise, we account for some error. + const distance = + Cartesian3.distance(result.center, sampledResult) - heightOffset; + const errorMargin = 100.0; + expect(distance).toBeLessThan(errorMargin); }); it("computes bounding sphere with height reference relative to ground on terrain provider without availability", function () { @@ -643,6 +629,7 @@ describe( // Repeatedly request the bounding sphere until it's ready. return pollToPromise(function () { scene.render(); + visualizer.update(time); state = visualizer.getBoundingSphere(testObject, result); return state !== BoundingSphereState.PENDING; }).then(() => { @@ -669,7 +656,7 @@ describe( expect(state).toBe(BoundingSphereState.FAILED); }); - it("fails bounding sphere when model fails to load", function () { + it("fails bounding sphere when model fails to load", async function () { const time = JulianDate.now(); const testObject = entityCollection.getOrCreateEntity("test"); const model = new ModelGraphics(); @@ -683,17 +670,17 @@ describe( const result = new BoundingSphere(); let state = visualizer.getBoundingSphere(testObject, result); - expect(state).toBe(BoundingSphereState.PENDING); - return pollToPromise(function () { + await pollToPromise(function () { scene.render(); + visualizer.update(time); state = visualizer.getBoundingSphere(testObject, result); return state !== BoundingSphereState.PENDING; - }).then(function () { - expect(state).toBe(BoundingSphereState.FAILED); }); + + expect(state).toBe(BoundingSphereState.FAILED); }); - it("fails bounding sphere when sampleTerrainMostDetailed fails", function () { + it("fails bounding sphere when sampleTerrainMostDetailed fails", async function () { // Setup a position for the model. const heightOffset = 1000.0; const position = Cartesian3.fromDegrees( @@ -724,8 +711,7 @@ describe( // Assign a tiled terrain provider to the globe. const globe = scene.globe; - const previousTerrainProvider = globe.terrainProvider; - globe.terrainProvider = createWorldTerrain(); + globe.terrainProvider = await createWorldTerrainAsync(); // Request the bounding sphere once. const result = new BoundingSphere(); @@ -734,6 +720,7 @@ describe( // Repeatedly request the bounding sphere until it's ready. return pollToPromise(function () { scene.render(); + visualizer.update(time); state = visualizer.getBoundingSphere(testObject, result); return state !== BoundingSphereState.PENDING; }).then(() => { @@ -745,8 +732,6 @@ describe( // Ensure that we only sample the terrain once from the visualizer. expect(sampleTerrainSpy).toHaveBeenCalledTimes(1); - // Reset the terrain provider. - globe.terrainProvider = previousTerrainProvider; }); }); diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js index 624d3b7a2f03..10bef3ed990d 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js @@ -293,7 +293,7 @@ describe( }); }); - it("loads json with static loadJson method", function () { + it("loads json with static loadJson method", async function () { const tilesetJson = { asset: { version: 2.0, @@ -301,14 +301,9 @@ describe( }; const uri = `data:text/plain;base64,${btoa(JSON.stringify(tilesetJson))}`; - - Cesium3DTileset.loadJson(uri) - .then(function (result) { - expect(result).toEqual(tilesetJson); - }) - .catch(function (error) { - fail("should not fail"); - }); + await expectAsync(Cesium3DTileset.loadJson(uri)).toBeResolvedTo( + tilesetJson + ); }); it("static method loadJson is used in Cesium3DTileset constructor", function () { @@ -329,7 +324,6 @@ describe( return tileset.readyPromise .then(function () { expect(tileset.ready).toEqual(true); - // restore original version Cesium3DTileset.loadJson = originalLoadJson; }) .catch(function (error) { @@ -597,108 +591,105 @@ describe( return tileset.readyPromise; }); - it("requests tile with invalid magic", function () { + it("requests tile with invalid magic", async function () { const invalidMagicBuffer = Cesium3DTilesTester.generateBatchedTileBuffer({ magic: [120, 120, 120, 120], }); options.url = tilesetUrl; const tileset = scene.primitives.add(new Cesium3DTileset(options)); - return tileset.readyPromise.then(function (tileset) { - // Start spying after the tileset json has been loaded - spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( - url, - responseType, - method, - data, - headers, - deferred, - overrideMimeType - ) { - deferred.resolve(invalidMagicBuffer); - }); - scene.renderForSpecs(); // Request root - const root = tileset.root; - return root.contentReadyPromise - .then(function () { - fail("should not resolve"); - }) - .catch(function (error) { - expect(error.message).toBe("Invalid tile content."); - expect(root._contentState).toEqual(Cesium3DTileContentState.FAILED); - }); + await tileset.readyPromise; + + const failedSpy = jasmine.createSpy("listenerSpy"); + tileset.tileFailed.addEventListener(failedSpy); + + // Start spying after the tileset json has been loaded + spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( + Promise.resolve(invalidMagicBuffer) + ); + + scene.renderForSpecs(); // Request root + const root = tileset.root; + await pollToPromise(() => { + scene.renderForSpecs(); + return root.contentFailed || root.contentReady; }); + + expect(failedSpy).toHaveBeenCalledWith( + jasmine.objectContaining({ + message: "Invalid tile content.", + }) + ); + expect(root.contentFailed).toBeTrue(); }); - it("handles failed tile requests", function () { + it("handles failed tile requests", async function () { viewRootOnly(); options.url = tilesetUrl; const tileset = scene.primitives.add(new Cesium3DTileset(options)); - return tileset.readyPromise.then(function (tileset) { - // Start spying after the tileset json has been loaded - spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( - url, - responseType, - method, - data, - headers, - deferred, - overrideMimeType - ) { - deferred.reject(new Error()); - }); - scene.renderForSpecs(); // Request root - const root = tileset.root; - return root.contentReadyPromise - .then(function () { - fail("should not resolve"); - }) - .catch(function (error) { - expect(root._contentState).toEqual(Cesium3DTileContentState.FAILED); - const statistics = tileset.statistics; - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(0); - expect(statistics.numberOfTilesProcessing).toBe(0); - expect(statistics.numberOfTilesWithContentReady).toBe(0); - }); + await tileset.readyPromise; + + const failedSpy = jasmine.createSpy("listenerSpy"); + tileset.tileFailed.addEventListener(failedSpy); + + // Start spying after the tileset json has been loaded + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(() => { + return Promise.reject(new Error("404")); + }); + scene.renderForSpecs(); // Request root + const root = tileset.root; + await pollToPromise(() => { + scene.renderForSpecs(); + return root.contentFailed || root.contentReady; }); + + expect(root.contentFailed).toBeTrue(); + expect(failedSpy).toHaveBeenCalledWith( + jasmine.objectContaining({ + message: "404", + }) + ); + const statistics = tileset.statistics; + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(0); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(0); }); - it("handles failed tile processing", function () { + it("handles failed tile processing", async function () { viewRootOnly(); options.url = tilesetUrl; const tileset = scene.primitives.add(new Cesium3DTileset(options)); - return tileset.readyPromise.then(function (tileset) { - // Start spying after the tileset json has been loaded - spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( - url, - responseType, - method, - data, - headers, - deferred, - overrideMimeType - ) { - deferred.resolve( - Cesium3DTilesTester.generateBatchedTileBuffer({ - version: 0, // Invalid version - }) - ); - }); - scene.renderForSpecs(); // Request root - const root = tileset.root; - return root.contentReadyPromise - .then(function () { - fail("should not resolve"); + await tileset.readyPromise; + + const failedSpy = jasmine.createSpy("listenerSpy"); + tileset.tileFailed.addEventListener(failedSpy); + + // Start spying after the tileset json has been loaded + spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue( + Promise.resolve( + Cesium3DTilesTester.generateBatchedTileBuffer({ + version: 0, // Invalid version }) - .catch(function (error) { - expect(root._contentState).toEqual(Cesium3DTileContentState.FAILED); - const statistics = tileset.statistics; - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(0); - expect(statistics.numberOfTilesProcessing).toBe(0); - expect(statistics.numberOfTilesWithContentReady).toBe(0); - }); + ) + ); + scene.renderForSpecs(); // Request root + const root = tileset.root; + await pollToPromise(() => { + scene.renderForSpecs(); + return root.contentFailed || root.contentReady; }); + expect(root.contentFailed).toBeTrue(); + expect(failedSpy).toHaveBeenCalledWith( + jasmine.objectContaining({ + message: + "Only Batched 3D Model version 1 is supported. Version 0 is not.", + }) + ); + const statistics = tileset.statistics; + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(0); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(0); }); it("renders tileset", function () { @@ -1544,7 +1535,7 @@ describe( }); }); - it("replacement refinement - selects root when sse is not met and subtree is not refinable (3)", function () { + it("replacement refinement - selects root when sse is not met and subtree is not refinable (3)", async function () { // Check that the root is refinable once its child is loaded // // C @@ -1554,31 +1545,30 @@ describe( // viewRootOnly(); - return Cesium3DTilesTester.loadTileset( + const tileset = await Cesium3DTilesTester.loadTileset( scene, tilesetReplacement3Url - ).then(function (tileset) { - tileset.skipLevelOfDetail = false; - const statistics = tileset._statistics; - const root = tileset.root; - expect(statistics.numberOfCommands).toEqual(1); + ); + tileset.skipLevelOfDetail = false; + const statistics = tileset._statistics; + const root = tileset.root; + expect(statistics.numberOfCommands).toEqual(1); - viewAllTiles(); + viewAllTiles(); + scene.renderForSpecs(); + await pollToPromise(() => { scene.renderForSpecs(); - return root.children[0].contentReadyPromise.then(function () { - // The external tileset json is loaded, but the external tileset isn't. - scene.renderForSpecs(); - expect(statistics.numberOfCommands).toEqual(1); // root - expect(statistics.numberOfPendingRequests).toEqual(4); // Loading child content tiles - - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( - function () { - expect(isSelected(tileset, root)).toEqual(false); - expect(statistics.numberOfCommands).toEqual(4); // Render child content tiles - } - ); - }); + return root.children[0].contentFailed || root.children[0].contentReady; }); + // The external tileset json is loaded, but the external tileset isn't. + scene.renderForSpecs(); + expect(statistics.numberOfCommands).toEqual(1); // root + expect(statistics.numberOfPendingRequests).toEqual(4); // Loading child content tiles + + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + + expect(isSelected(tileset, root)).toEqual(false); + expect(statistics.numberOfCommands).toEqual(4); // Render child content tiles }); it("replacement refinement - refines if descendant is empty leaf tile", function () { @@ -1906,41 +1896,44 @@ describe( }); }); - it("loads tileset with external tileset JSON file", function () { + it("loads tileset with external tileset JSON file", async function () { // Set view so that no tiles are loaded initially viewNothing(); - return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then( - function (tileset) { - // Root points to an external tileset JSON file and has no children until it is requested - const root = tileset.root; - expect(root.children.length).toEqual(0); - - // Set view so that root's content is requested - viewRootOnly(); - scene.renderForSpecs(); - return root.contentReadyPromise.then(function () { - expect(root.hasTilesetContent).toEqual(true); + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + tilesetOfTilesetsUrl + ); + // Root points to an external tileset JSON file and has no children until it is requested + const root = tileset.root; + expect(root.children.length).toEqual(0); - // Root has one child now, the root of the external tileset - expect(root.children.length).toEqual(1); + // Set view so that root's content is requested + viewRootOnly(); + scene.renderForSpecs(); + await pollToPromise(() => { + scene.renderForSpecs(); + return root.contentFailed || root.contentReady; + }); + expect(root.contentReady).toEqual(true); + expect(root.hasTilesetContent).toEqual(true); - // Check that headers are equal - const subtreeRoot = root.children[0]; - expect(root.refine).toEqual(subtreeRoot.refine); - expect(root.contentBoundingVolume.boundingVolume).toEqual( - subtreeRoot.contentBoundingVolume.boundingVolume - ); + // Root has one child now, the root of the external tileset + expect(root.children.length).toEqual(1); - // Check that subtree root has 4 children - expect(subtreeRoot.hasTilesetContent).toEqual(false); - expect(subtreeRoot.children.length).toEqual(4); - }); - } + // Check that headers are equal + const subtreeRoot = root.children[0]; + expect(root.refine).toEqual(subtreeRoot.refine); + expect(root.contentBoundingVolume.boundingVolume).toEqual( + subtreeRoot.contentBoundingVolume.boundingVolume ); + + // Check that subtree root has 4 children + expect(subtreeRoot.hasTilesetContent).toEqual(false); + expect(subtreeRoot.children.length).toEqual(4); }); - it("preserves query string with external tileset JSON file", function () { + it("preserves query string with external tileset JSON file", async function () { // Set view so that no tiles are loaded initially viewNothing(); @@ -1949,33 +1942,32 @@ describe( const queryParams = "a=1&b=boy"; let expectedUrl = `Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json?${queryParams}`; - return Cesium3DTilesTester.loadTileset( + const tileset = await Cesium3DTilesTester.loadTileset( scene, `${tilesetOfTilesetsUrl}?${queryParams}` - ) - .then(function (tileset) { - //Make sure tileset JSON file was requested with query parameters - expect( - Resource._Implementations.loadWithXhr.calls.argsFor(0)[0] - ).toEqual(expectedUrl); + ); + //Make sure tileset JSON file was requested with query parameters + expect(Resource._Implementations.loadWithXhr.calls.argsFor(0)[0]).toEqual( + expectedUrl + ); - Resource._Implementations.loadWithXhr.calls.reset(); + Resource._Implementations.loadWithXhr.calls.reset(); - // Set view so that root's content is requested - viewRootOnly(); - scene.renderForSpecs(); + // Set view so that root's content is requested + viewRootOnly(); + scene.renderForSpecs(); - return tileset.root.contentReadyPromise; - }) - .then(function () { - //Make sure tileset2.json was requested with query parameters and does not use parent tilesetVersion - expectedUrl = getAbsoluteUri( - `Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json?v=1.2.3&${queryParams}` - ); - expect( - Resource._Implementations.loadWithXhr.calls.argsFor(0)[0] - ).toEqual(expectedUrl); - }); + await pollToPromise(() => { + scene.renderForSpecs(); + return tileset.tilesLoaded; + }); + //Make sure tileset2.json was requested with query parameters and does not use parent tilesetVersion + expectedUrl = getAbsoluteUri( + `Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json?v=1.2.3&${queryParams}` + ); + expect(Resource._Implementations.loadWithXhr.calls.argsFor(0)[0]).toEqual( + expectedUrl + ); }); it("renders tileset with external tileset JSON file", function () { @@ -2052,7 +2044,6 @@ describe( expect(rgba).not.toEqual(color); }); - // Check for original color tileset.debugColorizeTiles = false; Cesium3DTilesTester.expectRender(scene, tileset, function (rgba) { expect(rgba).toEqual(color); @@ -2385,24 +2376,25 @@ describe( }); }); - it("does not process tiles when picking", function () { - const spy = spyOn(Cesium3DTile.prototype, "process").and.callThrough(); - + it("does not process tiles when picking", async function () { viewNothing(); - return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( - tileset - ) { - viewRootOnly(); - scene.renderForSpecs(); // Request root - expect(tileset._statistics.numberOfPendingRequests).toEqual(1); - return tileset.root.contentReadyToProcessPromise.then(function () { - scene.pickForSpecs(); - expect(spy).not.toHaveBeenCalled(); - scene.renderForSpecs(); - expect(spy).toHaveBeenCalled(); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); - }); + const tileset = await Cesium3DTilesTester.loadTileset(scene, tilesetUrl); + viewRootOnly(); + scene.renderForSpecs(); // Request root + expect(tileset._statistics.numberOfPendingRequests).toEqual(1); + await pollToPromise(() => { + scene.renderForSpecs(); + return ( + tileset.root._contentState === Cesium3DTileContentState.PROCESSING + ); }); + + const spy = spyOn(Cesium3DTile.prototype, "process").and.callThrough(); + scene.pickForSpecs(); + expect(spy).not.toHaveBeenCalled(); + + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + expect(spy).toHaveBeenCalled(); }); // https://github.com/CesiumGS/cesium/issues/6482 @@ -2593,43 +2585,47 @@ describe( }); }); - it("destroys before external tileset JSON file finishes loading", function () { + it("destroys before external tileset JSON file finishes loading", async function () { viewNothing(); - return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then( - function (tileset) { - const root = tileset.root; + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + tilesetOfTilesetsUrl + ); + const root = tileset.root; - viewRootOnly(); - scene.renderForSpecs(); // Request external tileset JSON file + viewRootOnly(); + scene.renderForSpecs(); // Request external tileset JSON file - const statistics = tileset._statistics; - expect(statistics.numberOfPendingRequests).toEqual(1); - scene.primitives.remove(tileset); + const statistics = tileset._statistics; + expect(statistics.numberOfPendingRequests).toEqual(1); + scene.primitives.remove(tileset); - return root.contentReadyPromise.then(function (content) { - expect(content).toBeUndefined(); + await pollToPromise(() => { + scene.renderForSpecs(); + return statistics.numberOfPendingRequests === 0; + }); - // Expect the root to not have added any children from the external tileset JSON file - expect(root.children.length).toEqual(0); - }); - } - ); + expect(root.content).toBeUndefined(); + + // Expect the root to not have added any children from the external tileset JSON file + expect(root.children.length).toEqual(0); }); - it("destroys before tile finishes loading", function () { + it("destroys before tile finishes loading", async function () { viewRootOnly(); options.url = tilesetUrl; const tileset = scene.primitives.add(new Cesium3DTileset(options)); - return tileset.readyPromise.then(function (tileset) { - const root = tileset.root; - scene.renderForSpecs(); // Request root - scene.primitives.remove(tileset); + await tileset.readyPromise; + const root = tileset.root; + scene.renderForSpecs(); // Request root + scene.primitives.remove(tileset); - return root.contentReadyPromise.then(function (content) { - expect(content).toBeUndefined(); - expect(root._contentState).toBe(Cesium3DTileContentState.FAILED); - }); + await pollToPromise(() => { + scene.renderForSpecs(); + return tileset._statistics.numberOfPendingRequests === 0; }); + + expect(root.content).toBeUndefined(); }); it("renders with imageBaseLightingFactor", function () { @@ -4075,7 +4071,6 @@ describe( it("tile expires", function () { return Cesium3DTilesTester.loadTileset(scene, batchedExpirationUrl).then( function (tileset) { - // Intercept the request and load content that produces more draw commands, to simulate fetching new content after the original expires spyOn(Resource._Implementations, "loadWithXhr").and.callFake( function ( url, @@ -4193,7 +4188,7 @@ describe( return Cesium3DTilesTester.loadTileset( scene, tilesetSubtreeExpirationUrl - ).then(function (tileset) { + ).then(async function (tileset) { // Intercept the request and load a subtree with one less child. Still want to make an actual request to simulate // real use cases instead of immediately returning a pre-created array buffer. spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( @@ -4267,52 +4262,42 @@ describe( }); }); - it("tile expires and request fails", function () { - return Cesium3DTilesTester.loadTileset(scene, batchedExpirationUrl).then( - function (tileset) { - spyOn(Resource._Implementations, "loadWithXhr").and.callFake( - function ( - url, - responseType, - method, - data, - headers, - deferred, - overrideMimeType - ) { - deferred.reject(new Error()); - } - ); - const tile = tileset.root; - const statistics = tileset._statistics; + it("tile expires and request fails", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + batchedExpirationUrl + ); + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(() => { + return Promise.reject(new Error("404")); + }); + const tile = tileset.root; + const statistics = tileset._statistics; - // Trigger expiration to happen next frame - tile.expireDate = JulianDate.addSeconds( - JulianDate.now(), - -1.0, - new JulianDate() - ); + // Trigger expiration to happen next frame + tile.expireDate = JulianDate.addSeconds( + JulianDate.now(), + -1.0, + new JulianDate() + ); - // After update the tile is expired - scene.renderForSpecs(); + const failedSpy = jasmine.createSpy("listenerSpy"); + tileset.tileFailed.addEventListener(failedSpy); - // Make request (it will fail) - scene.renderForSpecs(); + // After update the tile is expired + scene.renderForSpecs(); - return tile.contentReadyPromise - .then(function () { - fail(); - }) - .catch(function () { - // Render scene - scene.renderForSpecs(); + await pollToPromise(() => { + scene.renderForSpecs(); + return tileset.tilesLoaded; + }); - expect(tile._contentState).toBe(Cesium3DTileContentState.FAILED); - expect(statistics.numberOfCommands).toBe(0); - expect(statistics.numberOfTilesTotal).toBe(1); - }); - } + expect(failedSpy).toHaveBeenCalledWith( + jasmine.objectContaining({ + message: "404", + }) ); + expect(statistics.numberOfCommands).toBe(0); + expect(statistics.numberOfTilesTotal).toBe(1); }); it("tile expiration date", function () { @@ -4549,7 +4534,6 @@ describe( function (tileset) { // The bounding volume of this tileset puts it under the surface, so no // east-north-up should be applied. Check that it matches the orientation - // of the original transform. let offsetMatrix = tileset.clippingPlanesOriginMatrix; expect( @@ -4561,7 +4545,6 @@ describe( // The bounding volume of this tileset puts it on the surface, // so we want to apply east-north-up as our best guess. offsetMatrix = tileset.clippingPlanesOriginMatrix; - // The clipping plane matrix is not the same as the original because we applied east-north-up. expect( Matrix4.equals(offsetMatrix, tileset.root.computedTransform) ).toBe(false); @@ -5586,7 +5569,7 @@ describe( ); }); - it("request statistics are updated for partial success", function () { + it("request statistics are updated for partial success", async function () { const originalLoadJson = Cesium3DTileset.loadJson; spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) { return originalLoadJson(tilesetUrl).then(function (tilesetJson) { @@ -5601,41 +5584,39 @@ describe( }); viewNothing(); - let statistics; const tileset = scene.primitives.add( new Cesium3DTileset({ url: multipleContentsUrl, }) ); - return tileset.readyPromise - .then(function (tileset) { - viewAllTiles(); - scene.renderForSpecs(); + await tileset.readyPromise; + viewAllTiles(); + scene.renderForSpecs(); - statistics = tileset.statistics; - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(3); - expect(statistics.numberOfTilesProcessing).toBe(0); - expect(statistics.numberOfTilesWithContentReady).toBe(0); + const statistics = tileset.statistics; + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(3); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(0); - return tileset.root.contentReadyToProcessPromise; - }) - .then(function () { - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(0); - expect(statistics.numberOfTilesProcessing).toBe(1); - expect(statistics.numberOfTilesWithContentReady).toBe(0); + await pollToPromise(() => { + scene.renderForSpecs(); + return ( + tileset.root._contentState === Cesium3DTileContentState.PROCESSING + ); + }); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( - function () { - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(0); - expect(statistics.numberOfTilesProcessing).toBe(0); - expect(statistics.numberOfTilesWithContentReady).toBe(1); - } - ); - }); + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(0); + expect(statistics.numberOfTilesProcessing).toBe(1); + expect(statistics.numberOfTilesWithContentReady).toBe(0); + + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(0); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(1); }); it("request statistics are updated correctly if requests are not scheduled", function () { @@ -5660,50 +5641,50 @@ describe( ); }); - it("statistics update correctly if tile is canceled", function () { + it("statistics update correctly if tile is canceled", async function () { viewNothing(); - return Cesium3DTilesTester.loadTileset(scene, multipleContentsUrl).then( - function (tileset) { - let callCount = 0; - tileset.tileFailed.addEventListener(function (event) { - callCount++; - }); + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + multipleContentsUrl + ); + let callCount = 0; + tileset.tileFailed.addEventListener(function (event) { + callCount++; + }); - viewAllTiles(); - scene.renderForSpecs(); + viewAllTiles(); + scene.renderForSpecs(); - const statistics = tileset.statistics; - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(2); - expect(statistics.numberOfTilesProcessing).toBe(0); - expect(statistics.numberOfTilesWithContentReady).toBe(0); + const statistics = tileset.statistics; - const multipleContents = tileset.root.content; - multipleContents.cancelRequests(); + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(2); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(0); - tileset.root.contentReadyToProcessPromise - .then(function () { - expect(statistics.numberOfAttemptedRequests).toBe(2); - expect(statistics.numberOfPendingRequests).toBe(0); - expect(statistics.numberOfTilesProcessing).toBe(1); - expect(statistics.numberOfTilesWithContentReady).toBe(0); - }) - .catch(fail); + const multipleContents = tileset.root.content; + multipleContents.cancelRequests(); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( - function () { - // Resetting content should be handled gracefully; it should - // not trigger the tileFailed event - expect(callCount).toBe(0); - - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(0); - expect(statistics.numberOfTilesProcessing).toBe(0); - expect(statistics.numberOfTilesWithContentReady).toBe(1); - } - ); - } - ); + await pollToPromise(() => { + return ( + tileset.root._contentState !== Cesium3DTileContentState.LOADING + ); + }); + + expect(statistics.numberOfAttemptedRequests).toBe(2); + expect(statistics.numberOfPendingRequests).toBe(0); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(0); + + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + // Resetting content should be handled gracefully; it should + // not trigger the tileFailed event + expect(callCount).toBe(0); + + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(0); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(1); }); it("verify multiple content statistics", function () { @@ -5762,7 +5743,7 @@ describe( ); }); - it("raises tileFailed for external tileset inside multiple contents", function () { + it("raises tileFailed for external tileset inside multiple contents", async function () { const originalLoadJson = Cesium3DTileset.loadJson; spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) { return originalLoadJson(tilesetUrl).then(function (tilesetJson) { @@ -5780,17 +5761,6 @@ describe( }); }); - spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { - const externalTileset = { - asset: { - version: "1.1", - }, - root: {}, - }; - const buffer = generateJsonBuffer(externalTileset).buffer; - return Promise.resolve(buffer); - }); - viewNothing(); let errorCount = 0; const tileset = scene.primitives.add( @@ -5805,16 +5775,22 @@ describe( "External tilesets are disallowed inside multiple contents" ); }); - return tileset.readyPromise.then(function (tileset) { - viewAllTiles(); - scene.renderForSpecs(); + await tileset.readyPromise; - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( - function () { - expect(errorCount).toBe(2); - } - ); + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { + const externalTileset = { + asset: { + version: "1.1", + }, + root: {}, + }; + const buffer = generateJsonBuffer(externalTileset).buffer; + return Promise.resolve(buffer); }); + viewAllTiles(); + + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + expect(errorCount).toBe(2); }); it("debugColorizeTiles for multiple contents", function () { @@ -5893,7 +5869,7 @@ describe( }); }); - it("request statistics are updated for partial success (legacy)", function () { + it("request statistics are updated for partial success (legacy)", async function () { const originalLoadJson = Cesium3DTileset.loadJson; spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) { return originalLoadJson(tilesetUrl).then(function (tilesetJson) { @@ -5909,37 +5885,36 @@ describe( }); viewNothing(); - return Cesium3DTilesTester.loadTileset( + const tileset = await Cesium3DTilesTester.loadTileset( scene, multipleContentsLegacyUrl - ).then(function (tileset) { - viewAllTiles(); - scene.renderForSpecs(); - - const statistics = tileset.statistics; - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(3); - expect(statistics.numberOfTilesProcessing).toBe(0); - expect(statistics.numberOfTilesWithContentReady).toBe(0); + ); + viewAllTiles(); + scene.renderForSpecs(); - tileset.root.contentReadyToProcessPromise - .then(function () { - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(0); - expect(statistics.numberOfTilesProcessing).toBe(1); - expect(statistics.numberOfTilesWithContentReady).toBe(0); - }) - .catch(fail); + const statistics = tileset.statistics; + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(3); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(0); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( - function () { - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(0); - expect(statistics.numberOfTilesProcessing).toBe(0); - expect(statistics.numberOfTilesWithContentReady).toBe(1); - } + await pollToPromise(() => { + scene.renderForSpecs(); + return ( + tileset.root._contentState === Cesium3DTileContentState.PROCESSING ); }); + + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(0); + expect(statistics.numberOfTilesProcessing).toBe(1); + expect(statistics.numberOfTilesWithContentReady).toBe(0); + + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(0); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(1); }); it("request statistics are updated correctly if requests are not scheduled (legacy)", function () { @@ -5965,51 +5940,49 @@ describe( }); }); - it("statistics update correctly if tile is canceled (legacy)", function () { + it("statistics update correctly if tile is canceled (legacy)", async function () { viewNothing(); - return Cesium3DTilesTester.loadTileset( + const tileset = await Cesium3DTilesTester.loadTileset( scene, multipleContentsLegacyUrl - ).then(function (tileset) { - let callCount = 0; - tileset.tileFailed.addEventListener(function (event) { - callCount++; - }); - - viewAllTiles(); - scene.renderForSpecs(); + ); + let callCount = 0; + tileset.tileFailed.addEventListener(function (event) { + callCount++; + }); - const statistics = tileset.statistics; - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(2); - expect(statistics.numberOfTilesProcessing).toBe(0); - expect(statistics.numberOfTilesWithContentReady).toBe(0); + viewAllTiles(); + scene.renderForSpecs(); - const multipleContents = tileset.root.content; - multipleContents.cancelRequests(); + const statistics = tileset.statistics; + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(2); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(0); - tileset.root.contentReadyToProcessPromise - .then(function () { - expect(statistics.numberOfAttemptedRequests).toBe(2); - expect(statistics.numberOfPendingRequests).toBe(0); - expect(statistics.numberOfTilesProcessing).toBe(1); - expect(statistics.numberOfTilesWithContentReady).toBe(0); - }) - .catch(fail); + const multipleContents = tileset.root.content; + multipleContents.cancelRequests(); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( - function () { - // Resetting content should be handled gracefully; it should - // not trigger the tileFailed event - expect(callCount).toBe(0); - - expect(statistics.numberOfAttemptedRequests).toBe(0); - expect(statistics.numberOfPendingRequests).toBe(0); - expect(statistics.numberOfTilesProcessing).toBe(0); - expect(statistics.numberOfTilesWithContentReady).toBe(1); - } + await pollToPromise(() => { + return ( + tileset.root._contentState !== Cesium3DTileContentState.LOADING ); }); + + expect(statistics.numberOfAttemptedRequests).toBe(2); + expect(statistics.numberOfPendingRequests).toBe(0); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(0); + + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + // Resetting content should be handled gracefully; it should + // not trigger the tileFailed event + expect(callCount).toBe(0); + + expect(statistics.numberOfAttemptedRequests).toBe(0); + expect(statistics.numberOfPendingRequests).toBe(0); + expect(statistics.numberOfTilesProcessing).toBe(0); + expect(statistics.numberOfTilesWithContentReady).toBe(1); }); it("verify multiple content statistics (legacy)", function () { diff --git a/packages/engine/Specs/Scene/Composite3DTileContentSpec.js b/packages/engine/Specs/Scene/Composite3DTileContentSpec.js index cfcb57b66752..2d454a9912d8 100644 --- a/packages/engine/Specs/Scene/Composite3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Composite3DTileContentSpec.js @@ -5,6 +5,7 @@ import { ContentMetadata, HeadingPitchRange, MetadataClass, + RuntimeError, GroupMetadata, ImplicitMetadataView, } from "../../index.js"; @@ -84,14 +85,19 @@ describe( }); } - it("throws with invalid version", function () { + it("throws with invalid version", async function () { const arrayBuffer = Cesium3DTilesTester.generateCompositeTileBuffer({ version: 2, }); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "cmpt"); + await expectAsync( + Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "cmpt") + ).toBeRejectedWithError( + RuntimeError, + "Only Composite Tile version 1 is supported. Version 2 is not." + ); }); - it("throws with invalid inner tile content type", function () { + it("throws with invalid inner tile content type", async function () { const arrayBuffer = Cesium3DTilesTester.generateCompositeTileBuffer({ tiles: [ Cesium3DTilesTester.generateInstancedTileBuffer({ @@ -99,16 +105,25 @@ describe( }), ], }); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "cmpt"); + await expectAsync( + Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "cmpt") + ).toBeRejectedWithError( + RuntimeError, + "Unknown tile content type, xxxx, inside Composite tile" + ); }); - it("resolves readyPromise", function () { - return Cesium3DTilesTester.resolvesReadyPromise(scene, compositeUrl); + it("becomes ready", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + compositeUrl + ); + expect(tileset.root.contentReady).toBeTrue(); + expect(tileset.root.content).toBeDefined(); }); - it("rejects readyPromise on error", function () { + it("throws with invalid tile content", async function () { // Try loading a composite tile with an instanced tile that has an invalid url. - // Expect promise to be rejected in Model, Model3DTileContent and Composite3DTileContent. const arrayBuffer = Cesium3DTilesTester.generateCompositeTileBuffer({ tiles: [ Cesium3DTilesTester.generateInstancedTileBuffer({ @@ -117,10 +132,12 @@ describe( }), ], }); - return Cesium3DTilesTester.rejectsReadyPromiseOnError( - scene, - arrayBuffer, - "cmpt" + + await expectAsync( + Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "cmpt") + ).toBeRejectedWithError( + RuntimeError, + "Failed to load i3dm\nFailed to load glTF\nFailed to load glTF: http://localhost:8080/Specs/invalid?compositeIndex=0" ); }); diff --git a/packages/engine/Specs/Scene/Geometry3DTileContentSpec.js b/packages/engine/Specs/Scene/Geometry3DTileContentSpec.js index 946c0613b279..344dca690ce5 100644 --- a/packages/engine/Specs/Scene/Geometry3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Geometry3DTileContentSpec.js @@ -17,6 +17,7 @@ import { Rectangle, RectangleGeometry, RenderState, + RuntimeError, StencilConstants, } from "../../index.js"; import Cesium3DTilesTester from "../../../../Specs/Cesium3DTilesTester.js"; @@ -827,21 +828,31 @@ describe( }); }); - it("throws with invalid version", function () { + it("throws with invalid version", async function () { const arrayBuffer = Cesium3DTilesTester.generateGeometryTileBuffer({ version: 2, }); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "geom"); + await expectAsync( + Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "geom") + ).toBeRejectedWithError( + RuntimeError, + "Only Geometry tile version 1 is supported. Version 2 is not." + ); }); - it("throws with empty feature table", function () { + it("throws with empty async feature table", async function () { const arrayBuffer = Cesium3DTilesTester.generateGeometryTileBuffer({ defineFeatureTable: false, }); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "geom"); + await expectAsync( + Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "geom") + ).toBeRejectedWithError( + RuntimeError, + "Feature table must have a byte length greater than zero" + ); }); - it("throws without all batch ids", function () { + it("throws without all batch ids", async function () { const arrayBuffer = Cesium3DTilesTester.generateGeometryTileBuffer({ boxesLength: 1, cylindersLength: 1, @@ -851,7 +862,12 @@ describe( cylinderBatchIds: [0], ellipsoidBatchIds: [2], }); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "geom"); + await expectAsync( + Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "geom") + ).toBeRejectedWithError( + RuntimeError, + "If one group of batch ids is defined, then all batch ids must be defined" + ); }); it("destroys", function () { diff --git a/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js index dc5a3774f466..f5570011c99f 100644 --- a/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -1160,7 +1160,7 @@ describe( planes: [new ClippingPlane(Cartesian3.UNIT_Z, 10000000.0)], }); const model = scene.primitives.add( - await Model.fromGltf({ + await Model.fromGltfAsync({ url: "./Data/Models/glTF-2.0/BoxTextured/glTF/BoxTextured.gltf", }) ); diff --git a/packages/engine/Specs/Scene/GltfLoaderSpec.js b/packages/engine/Specs/Scene/GltfLoaderSpec.js index 0a7cecc8205b..8b8689061846 100644 --- a/packages/engine/Specs/Scene/GltfLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfLoaderSpec.js @@ -3340,8 +3340,7 @@ describe( }); }); - // TODO - xit("process throws if image resource fails to load", function () { + it("process throws if image resource fails to load", async function () { spyOn(Resource.prototype, "fetchImage").and.callFake(function () { const error = new Error("404 Not Found"); return Promise.reject(error); @@ -3368,31 +3367,18 @@ describe( const gltfLoader = new GltfLoader(getOptions(boxTextured, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene) - .then(function () { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load glTF\nFailed to load texture\nFailed to load image: CesiumLogoFlat.png\n404 Not Found" - ); - }) - .then(function () { - return gltfLoader.texturesLoadedPromise; - }) - .then(function () { - fail(); - }) - .catch(function (runtimeError) { - expect(runtimeError.message).toBe( - "Failed to load glTF\nFailed to load texture\nFailed to load image: CesiumLogoFlat.png\n404 Not Found" - ); - expect(destroyVertexBufferLoader.calls.count()).toBe(2); - expect(destroyIndexBufferLoader.calls.count()).toBe(1); - expect(destroyTextureLoader.calls.count()).toBe(1); - }); + await gltfLoader.load(); + + await expectAsync( + waitForLoaderProcess(gltfLoader, scene) + ).toBeRejectedWithError( + RuntimeError, + "Failed to load glTF\nFailed to load texture\nFailed to load image: CesiumLogoFlat.png\n404 Not Found" + ); + + expect(destroyVertexBufferLoader.calls.count()).toBe(2); + expect(destroyIndexBufferLoader.calls.count()).toBe(1); + expect(destroyTextureLoader.calls.count()).toBe(1); }); function resolveGltfJsonAfterDestroy(rejectPromise) { diff --git a/packages/engine/Specs/Scene/I3SNodeSpec.js b/packages/engine/Specs/Scene/I3SNodeSpec.js index afda3ea07085..6e6941b374ac 100644 --- a/packages/engine/Specs/Scene/I3SNodeSpec.js +++ b/packages/engine/Specs/Scene/I3SNodeSpec.js @@ -1425,16 +1425,12 @@ describe("Scene/I3SNode", function () { spyOn(childNode.tile, "_hookedRequestContent").and.callFake( function () { childNode.tile._contentReadyToProcessPromise = Promise.resolve(); - childNode.tile._contentReadyPromise = Promise.resolve(); } ); childNode.tile._contentResource = { _url: "mockOriginalContentUrl" }; childNode.tile.requestContent(); - return Promise.all([ - childNode.tile._contentReadyToProcessPromise, - childNode.tile._contentReadyPromise, - ]); + return Promise.all([childNode.tile._contentReadyToProcessPromise]); }) .then(function () { expect(childNode.tile._contentResource._url).toEqual("mockGlbUrl"); diff --git a/packages/engine/Specs/Scene/Implicit3DTileContentSpec.js b/packages/engine/Specs/Scene/Implicit3DTileContentSpec.js index 38b3a69fc6e0..e4c5025c9686 100644 --- a/packages/engine/Specs/Scene/Implicit3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Implicit3DTileContentSpec.js @@ -19,6 +19,7 @@ import { Model3DTileContent, Multiple3DTileContent, Resource, + ResourceCache, TileBoundingSphere, TileBoundingS2Cell, } from "../../index.js"; @@ -149,6 +150,7 @@ describe( let mockPlaceholderTile; beforeEach(function () { + ResourceCache.clearForSpecs(); mockTileset.statistics.numberOfTilesTotal = 0; mockPlaceholderTile = new Cesium3DTile(mockTileset, tilesetResource, { geometricError: 400, @@ -167,30 +169,29 @@ describe( afterEach(function () { scene.primitives.removeAll(); + ResourceCache.clearForSpecs(); }); - it("loads subtree using JSON", function () { - const content = new Implicit3DTileContent( + it("loads subtree using JSON", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, quadtreeJson ); - return content.readyPromise.then(function () { - const expectedChildrenCounts = [4, 0, 0, 0, 0]; - const tiles = []; - const subtreeRootTile = mockPlaceholderTile.children[0]; - gatherTilesPreorder(subtreeRootTile, 0, 1, tiles); - expect(expectedChildrenCounts.length).toEqual(tiles.length); - for (let i = 0; i < tiles.length; i++) { - expect(tiles[i].children.length).toEqual(expectedChildrenCounts[i]); - } - expect(mockTileset.statistics.numberOfTilesTotal).toBe(tiles.length); - }); + const expectedChildrenCounts = [4, 0, 0, 0, 0]; + const tiles = []; + const subtreeRootTile = mockPlaceholderTile.children[0]; + gatherTilesPreorder(subtreeRootTile, 0, 1, tiles); + expect(expectedChildrenCounts.length).toEqual(tiles.length); + for (let i = 0; i < tiles.length; i++) { + expect(tiles[i].children.length).toEqual(expectedChildrenCounts[i]); + } + expect(mockTileset.statistics.numberOfTilesTotal).toBe(tiles.length); }); - it("expands subtree", function () { - const content = new Implicit3DTileContent( + it("expands subtree", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -198,21 +199,19 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - const expectedChildrenCounts = [2, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0]; - const tiles = []; - const subtreeRootTile = mockPlaceholderTile.children[0]; - gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); - expect(expectedChildrenCounts.length).toEqual(tiles.length); - for (let i = 0; i < tiles.length; i++) { - expect(tiles[i].children.length).toEqual(expectedChildrenCounts[i]); - } - expect(mockTileset.statistics.numberOfTilesTotal).toBe(tiles.length); - }); + const expectedChildrenCounts = [2, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0]; + const tiles = []; + const subtreeRootTile = mockPlaceholderTile.children[0]; + gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); + expect(expectedChildrenCounts.length).toEqual(tiles.length); + for (let i = 0; i < tiles.length; i++) { + expect(tiles[i].children.length).toEqual(expectedChildrenCounts[i]); + } + expect(mockTileset.statistics.numberOfTilesTotal).toBe(tiles.length); }); - it("sets tile coordinates on each tile", function () { - const content = new Implicit3DTileContent( + it("sets tile coordinates on each tile", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -220,38 +219,36 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - const expectedCoordinates = [ - [0, 0, 0], - [1, 0, 0], - [2, 0, 0], - [2, 1, 0], - [2, 0, 1], - [2, 1, 1], - [1, 0, 1], - [2, 0, 2], - [2, 1, 2], - [2, 0, 3], - [2, 1, 3], - ]; - const tiles = []; - const subtreeRootTile = mockPlaceholderTile.children[0]; - gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); - for (let i = 0; i < tiles.length; i++) { - const expected = expectedCoordinates[i]; - const coordinates = new ImplicitTileCoordinates({ - subdivisionScheme: implicitTileset.subdivisionScheme, - subtreeLevels: implicitTileset.subtreeLevels, - level: expected[0], - x: expected[1], - y: expected[2], - }); - expect(tiles[i].implicitCoordinates).toEqual(coordinates); - } - }); + const expectedCoordinates = [ + [0, 0, 0], + [1, 0, 0], + [2, 0, 0], + [2, 1, 0], + [2, 0, 1], + [2, 1, 1], + [1, 0, 1], + [2, 0, 2], + [2, 1, 2], + [2, 0, 3], + [2, 1, 3], + ]; + const tiles = []; + const subtreeRootTile = mockPlaceholderTile.children[0]; + gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); + for (let i = 0; i < tiles.length; i++) { + const expected = expectedCoordinates[i]; + const coordinates = new ImplicitTileCoordinates({ + subdivisionScheme: implicitTileset.subdivisionScheme, + subtreeLevels: implicitTileset.subtreeLevels, + level: expected[0], + x: expected[1], + y: expected[2], + }); + expect(tiles[i].implicitCoordinates).toEqual(coordinates); + } }); - it("handles deeper subtrees correctly", function () { + it("handles deeper subtrees correctly", async function () { mockPlaceholderTile.implicitCoordinates = new ImplicitTileCoordinates({ subdivisionScheme: implicitTileset.subdivisionScheme, subtreeLevels: implicitTileset.subtreeLevels, @@ -259,7 +256,7 @@ describe( x: 2, y: 1, }); - const content = new Implicit3DTileContent( + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -292,29 +289,27 @@ describe( childCoordinates.y ); - return content.readyPromise.then(function () { - const subtreeRootTile = mockPlaceholderTile.children[0]; - const childTile = subtreeRootTile.children[0]; - expect(subtreeRootTile.implicitCoordinates).toEqual(parentCoordinates); - expect(childTile.implicitCoordinates).toEqual(childCoordinates); + const subtreeRootTile = mockPlaceholderTile.children[0]; + const childTile = subtreeRootTile.children[0]; + expect(subtreeRootTile.implicitCoordinates).toEqual(parentCoordinates); + expect(childTile.implicitCoordinates).toEqual(childCoordinates); - expect(subtreeRootTile.refine).toEqual(refine); - expect(childTile.refine).toEqual(refine); + expect(subtreeRootTile.refine).toEqual(refine); + expect(childTile.refine).toEqual(refine); - expect(subtreeRootTile.geometricError).toEqual(parentGeometricError); - expect(childTile.geometricError).toEqual(childGeometricError); + expect(subtreeRootTile.geometricError).toEqual(parentGeometricError); + expect(childTile.geometricError).toEqual(childGeometricError); - expect(getBoundingBoxArray(subtreeRootTile)).toEqual(parentBox); - expect(getBoundingBoxArray(childTile)).toEqual(childBox); + expect(getBoundingBoxArray(subtreeRootTile)).toEqual(parentBox); + expect(getBoundingBoxArray(childTile)).toEqual(childBox); - const tiles = []; - gatherTilesPreorder(subtreeRootTile, 2, 4, tiles); - expect(mockTileset.statistics.numberOfTilesTotal).toBe(tiles.length); - }); + const tiles = []; + gatherTilesPreorder(subtreeRootTile, 2, 4, tiles); + expect(mockTileset.statistics.numberOfTilesTotal).toBe(tiles.length); }); - it("puts the root tile inside the placeholder tile", function () { - const content = new Implicit3DTileContent( + it("puts the root tile inside the placeholder tile", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -322,13 +317,11 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - expect(mockPlaceholderTile.children.length).toEqual(1); - }); + expect(mockPlaceholderTile.children.length).toEqual(1); }); - it("preserves tile extras", function () { - const content = new Implicit3DTileContent( + it("preserves tile extras", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -336,13 +329,11 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - expect(mockPlaceholderTile.children[0].extras).toEqual(tileJson.extras); - }); + expect(mockPlaceholderTile.children[0].extras).toEqual(tileJson.extras); }); - it("stores a reference to the subtree in each transcoded tile", function () { - const content = new Implicit3DTileContent( + it("stores a reference to the subtree in each transcoded tile", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -350,23 +341,21 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - expect(mockPlaceholderTile.implicitSubtree).not.toBeDefined(); + expect(mockPlaceholderTile.implicitSubtree).not.toBeDefined(); - const subtreeRootTile = mockPlaceholderTile.children[0]; - const subtree = subtreeRootTile.implicitSubtree; - expect(subtree).toBeDefined(); + const subtreeRootTile = mockPlaceholderTile.children[0]; + const subtree = subtreeRootTile.implicitSubtree; + expect(subtree).toBeDefined(); - const tiles = []; - gatherTilesPreorder(subtreeRootTile, 0, 1, tiles); - for (let i = 0; i < tiles.length; i++) { - expect(tiles[i].implicitSubtree).toBe(subtree); - } - }); + const tiles = []; + gatherTilesPreorder(subtreeRootTile, 0, 1, tiles); + for (let i = 0; i < tiles.length; i++) { + expect(tiles[i].implicitSubtree).toBe(subtree); + } }); - it("does not store references to subtrees in placeholder tiles", function () { - const content = new Implicit3DTileContent( + it("does not store references to subtrees in placeholder tiles", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -374,21 +363,19 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - expect(mockPlaceholderTile.implicitSubtree).not.toBeDefined(); + expect(mockPlaceholderTile.implicitSubtree).not.toBeDefined(); - const subtreeRootTile = mockPlaceholderTile.children[0]; + const subtreeRootTile = mockPlaceholderTile.children[0]; - const tiles = []; - gatherTilesPreorder(subtreeRootTile, 2, 2, tiles); - for (let i = 0; i < tiles.length; i++) { - expect(tiles[i].implicitSubtree).not.toBeDefined(); - } - }); + const tiles = []; + gatherTilesPreorder(subtreeRootTile, 2, 2, tiles); + for (let i = 0; i < tiles.length; i++) { + expect(tiles[i].implicitSubtree).not.toBeDefined(); + } }); - it("destroys", function () { - const content = new Implicit3DTileContent( + it("destroys", async function () { + const content = await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -396,19 +383,17 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - const subtree = content._implicitSubtree; - expect(content.isDestroyed()).toBe(false); - expect(subtree.isDestroyed()).toBe(false); - - content.destroy(); - expect(content.isDestroyed()).toBe(true); - expect(subtree.isDestroyed()).toBe(true); - }); + const subtree = content._implicitSubtree; + expect(content.isDestroyed()).toBe(false); + expect(subtree.isDestroyed()).toBe(false); + + content.destroy(); + expect(content.isDestroyed()).toBe(true); + expect(subtree.isDestroyed()).toBe(true); }); - it("returns default values for most Cesium3DTileContent properties", function () { - const content = new Implicit3DTileContent( + it("returns default values for most Cesium3DTileContent properties", async function () { + const content = await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -430,8 +415,8 @@ describe( expect(content.batchTable).not.toBeDefined(); }); - it("url returns the subtree url", function () { - const content = new Implicit3DTileContent( + it("url returns the subtree url", async function () { + const content = await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -442,8 +427,8 @@ describe( expect(content.url).toBe("https://example.com/0/0/0.subtree"); }); - it("templates content URIs for each tile with content", function () { - const content = new Implicit3DTileContent( + it("templates content URIs for each tile with content", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -451,41 +436,39 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - const expectedCoordinates = [ - [0, 0, 0], - [1, 0, 0], - [1, 0, 1], - ]; - const contentAvailability = [false, true, true]; - const templateUri = implicitTileset.contentUriTemplates[0]; - const subtreeRootTile = mockPlaceholderTile.children[0]; - const tiles = []; - gatherTilesPreorder(subtreeRootTile, 0, 1, tiles); - expect(expectedCoordinates.length).toEqual(tiles.length); - for (let i = 0; i < tiles.length; i++) { - const expected = expectedCoordinates[i]; - const coordinates = new ImplicitTileCoordinates({ - subdivisionScheme: implicitTileset.subdivisionScheme, - subtreeLevels: implicitTileset.subtreeLevels, - level: expected[0], - x: expected[1], - y: expected[2], - }); - const expectedResource = templateUri.getDerivedResource({ - templateValues: coordinates.getTemplateValues(), - }); - if (contentAvailability[i]) { - expect(tiles[i]._contentResource.url).toEqual(expectedResource.url); - } else { - expect(tiles[i]._contentResource).not.toBeDefined(); - } + const expectedCoordinates = [ + [0, 0, 0], + [1, 0, 0], + [1, 0, 1], + ]; + const contentAvailability = [false, true, true]; + const templateUri = implicitTileset.contentUriTemplates[0]; + const subtreeRootTile = mockPlaceholderTile.children[0]; + const tiles = []; + gatherTilesPreorder(subtreeRootTile, 0, 1, tiles); + expect(expectedCoordinates.length).toEqual(tiles.length); + for (let i = 0; i < tiles.length; i++) { + const expected = expectedCoordinates[i]; + const coordinates = new ImplicitTileCoordinates({ + subdivisionScheme: implicitTileset.subdivisionScheme, + subtreeLevels: implicitTileset.subtreeLevels, + level: expected[0], + x: expected[1], + y: expected[2], + }); + const expectedResource = templateUri.getDerivedResource({ + templateValues: coordinates.getTemplateValues(), + }); + if (contentAvailability[i]) { + expect(tiles[i]._contentResource.url).toEqual(expectedResource.url); + } else { + expect(tiles[i]._contentResource).not.toBeDefined(); } - }); + } }); - it("constructs placeholder tiles for child subtrees", function () { - const content = new Implicit3DTileContent( + it("constructs placeholder tiles for child subtrees", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -493,51 +476,49 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - const expectedCoordinates = [ - [2, 0, 0], - [2, 1, 0], - [2, 0, 1], - [2, 1, 1], - [2, 0, 2], - [2, 1, 2], - [2, 0, 3], - [2, 1, 3], - ]; - const templateUri = implicitTileset.subtreeUriTemplate; - const subtreeRootTile = mockPlaceholderTile.children[0]; - let tiles = []; - gatherTilesPreorder(subtreeRootTile, 2, 2, tiles); - - expect(expectedCoordinates.length).toEqual(tiles.length); - for (let i = 0; i < tiles.length; i++) { - const expected = expectedCoordinates[i]; - const coordinates = new ImplicitTileCoordinates({ - subdivisionScheme: implicitTileset.subdivisionScheme, - subtreeLevels: implicitTileset.subtreeLevels, - level: expected[0], - x: expected[1], - y: expected[2], - }); - const expectedResource = templateUri.getDerivedResource({ - templateValues: coordinates.getTemplateValues(), - }); - const placeholderTile = tiles[i]; - expect(placeholderTile._contentResource.url).toEqual( - expectedResource.url - ); - expect(placeholderTile.implicitTileset).toBeDefined(); - expect(placeholderTile.implicitCoordinates).toBeDefined(); - } + const expectedCoordinates = [ + [2, 0, 0], + [2, 1, 0], + [2, 0, 1], + [2, 1, 1], + [2, 0, 2], + [2, 1, 2], + [2, 0, 3], + [2, 1, 3], + ]; + const templateUri = implicitTileset.subtreeUriTemplate; + const subtreeRootTile = mockPlaceholderTile.children[0]; + let tiles = []; + gatherTilesPreorder(subtreeRootTile, 2, 2, tiles); + + expect(expectedCoordinates.length).toEqual(tiles.length); + for (let i = 0; i < tiles.length; i++) { + const expected = expectedCoordinates[i]; + const coordinates = new ImplicitTileCoordinates({ + subdivisionScheme: implicitTileset.subdivisionScheme, + subtreeLevels: implicitTileset.subtreeLevels, + level: expected[0], + x: expected[1], + y: expected[2], + }); + const expectedResource = templateUri.getDerivedResource({ + templateValues: coordinates.getTemplateValues(), + }); + const placeholderTile = tiles[i]; + expect(placeholderTile._contentResource.url).toEqual( + expectedResource.url + ); + expect(placeholderTile.implicitTileset).toBeDefined(); + expect(placeholderTile.implicitCoordinates).toBeDefined(); + } - tiles = []; - gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); - expect(mockTileset.statistics.numberOfTilesTotal).toBe(tiles.length); - }); + tiles = []; + gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); + expect(mockTileset.statistics.numberOfTilesTotal).toBe(tiles.length); }); - it("propagates refine down the tree", function () { - const content = new Implicit3DTileContent( + it("propagates refine down the tree", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -549,18 +530,16 @@ describe( implicitTileset.refine === "ADD" ? Cesium3DTileRefine.ADD : Cesium3DTileRefine.REPLACE; - return content.readyPromise.then(function () { - const subtreeRootTile = mockPlaceholderTile.children[0]; - const tiles = []; - gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); - for (let i = 0; i < tiles.length; i++) { - expect(tiles[i].refine).toEqual(refine); - } - }); + const subtreeRootTile = mockPlaceholderTile.children[0]; + const tiles = []; + gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); + for (let i = 0; i < tiles.length; i++) { + expect(tiles[i].refine).toEqual(refine); + } }); - it("divides the geometricError by 2 for each level of the tree", function () { - const content = new Implicit3DTileContent( + it("divides the geometricError by 2 for each level of the tree", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -569,21 +548,19 @@ describe( 0 ); const rootGeometricError = implicitTileset.geometricError; - return content.readyPromise.then(function () { - const subtreeRootTile = mockPlaceholderTile.children[0]; - const tiles = []; - gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); - for (let i = 0; i < tiles.length; i++) { - const level = tiles[i].implicitCoordinates.level; - expect(tiles[i].geometricError).toEqual( - rootGeometricError / Math.pow(2, level) - ); - } - }); + const subtreeRootTile = mockPlaceholderTile.children[0]; + const tiles = []; + gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); + for (let i = 0; i < tiles.length; i++) { + const level = tiles[i].implicitCoordinates.level; + expect(tiles[i].geometricError).toEqual( + rootGeometricError / Math.pow(2, level) + ); + } }); - it("subdivides bounding volumes for each tile", function () { - const content = new Implicit3DTileContent( + it("subdivides bounding volumes for each tile", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -591,48 +568,46 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - const expectedCoordinates = [ - [0, 0, 0], - [1, 0, 0], - [2, 0, 0], - [2, 1, 0], - [2, 0, 1], - [2, 1, 1], - [1, 0, 1], - [2, 0, 2], - [2, 1, 2], - [2, 0, 3], - [2, 1, 3], - ]; - const rootBoundingVolume = [10, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 256]; - - const subtreeRootTile = mockPlaceholderTile.children[0]; - const tiles = []; - gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); - - expect(expectedCoordinates.length).toEqual(tiles.length); - for (let i = 0; i < tiles.length; i++) { - const coordinates = expectedCoordinates[i]; - - const boundingBox = tiles[i].boundingVolume.boundingVolume; - const childBox = new Array(12); - Cartesian3.pack(boundingBox.center, childBox); - Matrix3.pack(boundingBox.halfAxes, childBox, 3); - - const expectedBounds = Implicit3DTileContent._deriveBoundingBox( - rootBoundingVolume, - coordinates[0], - coordinates[1], - coordinates[2] - ); - expect(childBox).toEqual(expectedBounds); - } - }); + const expectedCoordinates = [ + [0, 0, 0], + [1, 0, 0], + [2, 0, 0], + [2, 1, 0], + [2, 0, 1], + [2, 1, 1], + [1, 0, 1], + [2, 0, 2], + [2, 1, 2], + [2, 0, 3], + [2, 1, 3], + ]; + const rootBoundingVolume = [10, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 256]; + + const subtreeRootTile = mockPlaceholderTile.children[0]; + const tiles = []; + gatherTilesPreorder(subtreeRootTile, 0, 2, tiles); + + expect(expectedCoordinates.length).toEqual(tiles.length); + for (let i = 0; i < tiles.length; i++) { + const coordinates = expectedCoordinates[i]; + + const boundingBox = tiles[i].boundingVolume.boundingVolume; + const childBox = new Array(12); + Cartesian3.pack(boundingBox.center, childBox); + Matrix3.pack(boundingBox.halfAxes, childBox, 3); + + const expectedBounds = Implicit3DTileContent._deriveBoundingBox( + rootBoundingVolume, + coordinates[0], + coordinates[1], + coordinates[2] + ); + expect(childBox).toEqual(expectedBounds); + } }); - it("propagates transform", function () { - const content = new Implicit3DTileContent( + it("propagates transform", async function () { + await Implicit3DTileContent.fromSubtreeJson( mockTileset, mockPlaceholderTile, tilesetResource, @@ -640,12 +615,10 @@ describe( quadtreeBuffer, 0 ); - return content.readyPromise.then(function () { - const subtreeRootTile = mockPlaceholderTile.children[0]; - expect(subtreeRootTile.computedTransform).toEqual( - mockPlaceholderTile.transform - ); - }); + const subtreeRootTile = mockPlaceholderTile.children[0]; + expect(subtreeRootTile.computedTransform).toEqual( + mockPlaceholderTile.transform + ); }); describe("_deriveBoundingVolumeS2", function () { diff --git a/packages/engine/Specs/Scene/ImplicitMetadataViewSpec.js b/packages/engine/Specs/Scene/ImplicitMetadataViewSpec.js index 1a5915a8b2f4..089d3a3de96e 100644 --- a/packages/engine/Specs/Scene/ImplicitMetadataViewSpec.js +++ b/packages/engine/Specs/Scene/ImplicitMetadataViewSpec.js @@ -230,12 +230,12 @@ describe("Scene/ImplicitMetadataView", function () { let treeView; let secondTreeView; - beforeEach(function () { + beforeEach(async function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - subtree = new ImplicitSubtree( + subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -243,52 +243,50 @@ describe("Scene/ImplicitMetadataView", function () { rootCoordinates ); - return subtree.readyPromise.then(function () { - tileView = new ImplicitMetadataView({ - metadataTable: subtree.tileMetadataTable, - class: tileClass, - entityId: 0, - propertyTableJson: emptyJson, - }); + tileView = new ImplicitMetadataView({ + metadataTable: subtree.tileMetadataTable, + class: tileClass, + entityId: 0, + propertyTableJson: emptyJson, + }); - secondTileView = new ImplicitMetadataView({ - metadataTable: subtree.tileMetadataTable, - class: tileClass, - entityId: 1, - propertyTableJson: emptyJson, - }); + secondTileView = new ImplicitMetadataView({ + metadataTable: subtree.tileMetadataTable, + class: tileClass, + entityId: 1, + propertyTableJson: emptyJson, + }); - buildingView = new ImplicitMetadataView({ - metadataTable: subtree.contentMetadataTables[0], - class: buildingClass, - entityId: 0, - contentIndex: 0, - propertyTableJson: emptyJson, - }); + buildingView = new ImplicitMetadataView({ + metadataTable: subtree.contentMetadataTables[0], + class: buildingClass, + entityId: 0, + contentIndex: 0, + propertyTableJson: emptyJson, + }); - secondBuildingView = new ImplicitMetadataView({ - metadataTable: subtree.contentMetadataTables[0], - class: buildingClass, - entityId: 1, - contentIndex: 0, - propertyTableJson: emptyJson, - }); + secondBuildingView = new ImplicitMetadataView({ + metadataTable: subtree.contentMetadataTables[0], + class: buildingClass, + entityId: 1, + contentIndex: 0, + propertyTableJson: emptyJson, + }); - treeView = new ImplicitMetadataView({ - metadataTable: subtree.contentMetadataTables[1], - class: treeClass, - entityId: 0, - contentIndex: 1, - propertyTableJson: emptyJson, - }); + treeView = new ImplicitMetadataView({ + metadataTable: subtree.contentMetadataTables[1], + class: treeClass, + entityId: 0, + contentIndex: 1, + propertyTableJson: emptyJson, + }); - secondTreeView = new ImplicitMetadataView({ - metadataTable: subtree.contentMetadataTables[1], - class: treeClass, - entityId: 1, - contentIndex: 1, - propertyTableJson: emptyJson, - }); + secondTreeView = new ImplicitMetadataView({ + metadataTable: subtree.contentMetadataTables[1], + class: treeClass, + entityId: 1, + contentIndex: 1, + propertyTableJson: emptyJson, }); }); diff --git a/packages/engine/Specs/Scene/ImplicitSubtreeCacheSpec.js b/packages/engine/Specs/Scene/ImplicitSubtreeCacheSpec.js index 899f6935fefc..9cfafaac261a 100644 --- a/packages/engine/Specs/Scene/ImplicitSubtreeCacheSpec.js +++ b/packages/engine/Specs/Scene/ImplicitSubtreeCacheSpec.js @@ -65,7 +65,7 @@ describe("Scene/ImplicitSubtreeCache", function () { expect(cache._subtreeRequestCounter).toBe(0); }); - it("can add a subtree", function () { + it("can add a subtree", async function () { const cache = new ImplicitSubtreeCache(); const octreeCoordinates = new ImplicitTileCoordinates({ subdivisionScheme: implicitOctree.subdivisionScheme, @@ -75,7 +75,7 @@ describe("Scene/ImplicitSubtreeCache", function () { y: 0, z: 0, }); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, subtreeConstantJson, undefined, @@ -86,7 +86,7 @@ describe("Scene/ImplicitSubtreeCache", function () { expect(cache._subtreeRequestCounter).toBe(1); }); - it("addSubtree throws if parent does not exist", function () { + it("addSubtree throws if parent does not exist", async function () { const cache = new ImplicitSubtreeCache(); const octreeCoordinates = new ImplicitTileCoordinates({ subdivisionScheme: implicitOctree.subdivisionScheme, @@ -96,7 +96,7 @@ describe("Scene/ImplicitSubtreeCache", function () { y: 1, z: 0, }); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, subtreeConstantJson, undefined, @@ -106,7 +106,7 @@ describe("Scene/ImplicitSubtreeCache", function () { expect(() => cache.addSubtree(subtree)).toThrowDeveloperError(); }); - it("addSubtree trims cache as needed", function () { + it("addSubtree trims cache as needed", async function () { const cache = new ImplicitSubtreeCache({ maximumSubtreeCount: 3, }); @@ -120,19 +120,21 @@ describe("Scene/ImplicitSubtreeCache", function () { subdivisionScheme: implicitOctree.subdivisionScheme, subtreeLevels: implicitOctree.subtreeLevels, }; - octreeCoordArray.forEach((octreeCoord) => { - const octreeCoordinates = new ImplicitTileCoordinates( - Object.assign({}, octreeCoordParams, octreeCoord) - ); - const subtree = new ImplicitSubtree( - subtreeResource, - subtreeConstantJson, - undefined, - implicitOctree, - octreeCoordinates - ); - cache.addSubtree(subtree); - }); + await Promise.all( + octreeCoordArray.map(async (octreeCoord) => { + const octreeCoordinates = new ImplicitTileCoordinates( + Object.assign({}, octreeCoordParams, octreeCoord) + ); + const subtree = await ImplicitSubtree.fromSubtreeJson( + subtreeResource, + subtreeConstantJson, + undefined, + implicitOctree, + octreeCoordinates + ); + cache.addSubtree(subtree); + }) + ); expect(cache._subtreeRequestCounter).toBe(4); expect(cache._queue.length).toBe(3); }); diff --git a/packages/engine/Specs/Scene/ImplicitSubtreeSpec.js b/packages/engine/Specs/Scene/ImplicitSubtreeSpec.js index ab612f2acab4..7e275c53ae28 100644 --- a/packages/engine/Specs/Scene/ImplicitSubtreeSpec.js +++ b/packages/engine/Specs/Scene/ImplicitSubtreeSpec.js @@ -91,12 +91,15 @@ describe("Scene/ImplicitSubtree", function () { } // used for spying on ResourceCache.load() - function fakeLoad(arrayBuffer) { + function fakeResourceLoader(arrayBuffer) { return function (options) { const fakeCacheResource = { typedArray: arrayBuffer, + load: async () => {}, + process: () => true, + isDestroyed: () => false, }; - options.resourceLoader._promise = Promise.resolve(fakeCacheResource); + return fakeCacheResource; }; } @@ -243,24 +246,7 @@ describe("Scene/ImplicitSubtree", function () { it("throws without resource", function () { expect(function () { - const results = ImplicitTilingTester.generateSubtreeBuffers( - internalQuadtreeDescription - ); return new ImplicitSubtree( - undefined, - undefined, - results.subtreeBuffer, - implicitQuadtree, - quadtreeCoordinates - ); - }).toThrowDeveloperError(); - }); - - it("throws without json or subtreeView", function () { - expect(function () { - return new ImplicitSubtree( - subtreeResource, - undefined, undefined, implicitQuadtree, quadtreeCoordinates @@ -270,14 +256,9 @@ describe("Scene/ImplicitSubtree", function () { it("throws without implicitTileset", function () { expect(function () { - const results = ImplicitTilingTester.generateSubtreeBuffers( - internalQuadtreeDescription - ); return new ImplicitSubtree( subtreeResource, undefined, - results.subtreeBuffer, - undefined, quadtreeCoordinates ); }).toThrowDeveloperError(); @@ -285,24 +266,15 @@ describe("Scene/ImplicitSubtree", function () { it("throws without implicitCoordinates", function () { expect(function () { - const results = ImplicitTilingTester.generateSubtreeBuffers( - internalQuadtreeDescription - ); - return new ImplicitSubtree( - subtreeResource, - undefined, - results.subtreeBuffer, - implicitQuadtree, - undefined - ); + return new ImplicitSubtree(subtreeResource, implicitQuadtree, undefined); }).toThrowDeveloperError(); }); - it("sets the implicit coordinates of the subtree's root", function () { + it("sets the implicit coordinates of the subtree's root", async function () { const results = ImplicitTilingTester.generateSubtreeBuffers( internalQuadtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -315,34 +287,44 @@ describe("Scene/ImplicitSubtree", function () { ); }); - it("gets availability from internal buffer", function () { + it("ImplicitSubtree.fromSubtreeJson throws without json or subtreeView", async function () { + await expectAsync( + ImplicitSubtree.fromSubtreeJson( + subtreeResource, + undefined, + undefined, + implicitQuadtree, + quadtreeCoordinates + ) + ).toBeRejectedWithDeveloperError(); + }); + + it("gets availability from internal buffer", async function () { const results = ImplicitTilingTester.generateSubtreeBuffers( internalQuadtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, implicitQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability( - subtree, - internalQuadtreeDescription.tileAvailability - ); - expectContentAvailability( - subtree, - internalQuadtreeDescription.contentAvailability - ); - expectChildSubtreeAvailability( - subtree, - internalQuadtreeDescription.childSubtreeAvailability - ); - }); + expectTileAvailability( + subtree, + internalQuadtreeDescription.tileAvailability + ); + expectContentAvailability( + subtree, + internalQuadtreeDescription.contentAvailability + ); + expectChildSubtreeAvailability( + subtree, + internalQuadtreeDescription.childSubtreeAvailability + ); }); - it("gets availability from external buffer", function () { + it("gets availability from external buffer", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11010", @@ -365,33 +347,28 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, implicitQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability(subtree, subtreeDescription.tileAvailability); - expectContentAvailability( - subtree, - subtreeDescription.contentAvailability - ); - expectChildSubtreeAvailability( - subtree, - subtreeDescription.childSubtreeAvailability - ); + expectTileAvailability(subtree, subtreeDescription.tileAvailability); + expectContentAvailability(subtree, subtreeDescription.contentAvailability); + expectChildSubtreeAvailability( + subtree, + subtreeDescription.childSubtreeAvailability + ); - expect(fetchExternal.calls.count()).toEqual(1); - }); + expect(fetchExternal.calls.count()).toEqual(1); }); - it("gets availability from JSON constants", function () { - const subtree = new ImplicitSubtree( + it("gets availability from JSON constants", async function () { + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, subtreeConstantJson, undefined, @@ -399,23 +376,21 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability( - subtree, - constantQuadtreeDescription.tileAvailability - ); - expectContentAvailability( - subtree, - constantQuadtreeDescription.contentAvailability - ); - expectChildSubtreeAvailability( - subtree, - constantQuadtreeDescription.childSubtreeAvailability - ); - }); + expectTileAvailability( + subtree, + constantQuadtreeDescription.tileAvailability + ); + expectContentAvailability( + subtree, + constantQuadtreeDescription.contentAvailability + ); + expectChildSubtreeAvailability( + subtree, + constantQuadtreeDescription.childSubtreeAvailability + ); }); - it("gets availability from JSON and external buffers", function () { + it("gets availability from JSON and external buffers", async function () { const description = { tileAvailability: { descriptor: "11010", @@ -439,11 +414,11 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers(description); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, results.subtreeJson, undefined, @@ -451,26 +426,24 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability(subtree, description.tileAvailability); - expectContentAvailability(subtree, description.contentAvailability); - expectChildSubtreeAvailability( - subtree, - description.childSubtreeAvailability - ); + expectTileAvailability(subtree, description.tileAvailability); + expectContentAvailability(subtree, description.contentAvailability); + expectChildSubtreeAvailability( + subtree, + description.childSubtreeAvailability + ); - expect(fetchExternal.calls.count()).toEqual(1); - }); + expect(fetchExternal.calls.count()).toEqual(1); }); // Test for backwards compatibility - it("gets availability with bufferViews", function () { + it("gets availability with bufferViews", async function () { const description = clone(internalQuadtreeDescription); description.useLegacySchema = true; const results = ImplicitTilingTester.generateSubtreeBuffers(description); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -478,23 +451,21 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability( - subtree, - internalQuadtreeDescription.tileAvailability - ); - expectContentAvailability( - subtree, - internalQuadtreeDescription.contentAvailability - ); - expectChildSubtreeAvailability( - subtree, - internalQuadtreeDescription.childSubtreeAvailability - ); - }); + expectTileAvailability( + subtree, + internalQuadtreeDescription.tileAvailability + ); + expectContentAvailability( + subtree, + internalQuadtreeDescription.contentAvailability + ); + expectChildSubtreeAvailability( + subtree, + internalQuadtreeDescription.childSubtreeAvailability + ); }); - it("handles typed arrays with a byte offset", function () { + it("handles typed arrays with a byte offset", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11010", @@ -527,27 +498,22 @@ describe("Scene/ImplicitSubtree", function () { biggerBuffer.set(results.subtreeBuffer, paddingLength); const subtreeView = new Uint8Array(biggerBuffer.buffer, paddingLength); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, subtreeView, implicitQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability(subtree, subtreeDescription.tileAvailability); - expectContentAvailability( - subtree, - subtreeDescription.contentAvailability - ); - expectChildSubtreeAvailability( - subtree, - subtreeDescription.childSubtreeAvailability - ); - }); + expectTileAvailability(subtree, subtreeDescription.tileAvailability); + expectContentAvailability(subtree, subtreeDescription.contentAvailability); + expectChildSubtreeAvailability( + subtree, + subtreeDescription.childSubtreeAvailability + ); }); - it("tile and content availability can share the same buffer", function () { + it("tile and content availability can share the same buffer", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11010", @@ -572,31 +538,26 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, implicitQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability(subtree, subtreeDescription.tileAvailability); - expectContentAvailability( - subtree, - subtreeDescription.contentAvailability - ); - expectChildSubtreeAvailability( - subtree, - subtreeDescription.childSubtreeAvailability - ); - expect(fetchExternal.calls.count()).toEqual(1); - }); + expectTileAvailability(subtree, subtreeDescription.tileAvailability); + expectContentAvailability(subtree, subtreeDescription.contentAvailability); + expectChildSubtreeAvailability( + subtree, + subtreeDescription.childSubtreeAvailability + ); + expect(fetchExternal.calls.count()).toEqual(1); }); - it("external buffer is fetched if it is used for availability", function () { + it("external buffer is fetched if it is used for availability", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -620,22 +581,20 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, implicitQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal.calls.count()).toEqual(1); - }); + expect(fetchExternal.calls.count()).toEqual(1); }); - it("unused external buffers are not fetched", function () { + it("unused external buffers are not fetched", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -668,19 +627,17 @@ describe("Scene/ImplicitSubtree", function () { Resource.prototype, "fetchArrayBuffer" ).and.returnValue(Promise.resolve(results.externalBuffer)); - const subtree = new ImplicitSubtree( + await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, implicitQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); - }); + expect(fetchExternal).not.toHaveBeenCalled(); }); - it("missing contentAvailability is interpreted as 0s", function () { + it("missing contentAvailability is interpreted as 0s", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11010", @@ -703,24 +660,22 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, implicitQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability(subtree, subtreeDescription.tileAvailability); - expectContentAvailability(subtree, expectedContentAvailability); - expectChildSubtreeAvailability( - subtree, - subtreeDescription.childSubtreeAvailability - ); - }); + expectTileAvailability(subtree, subtreeDescription.tileAvailability); + expectContentAvailability(subtree, expectedContentAvailability); + expectChildSubtreeAvailability( + subtree, + subtreeDescription.childSubtreeAvailability + ); }); - it("availability works for quadtrees", function () { + it("availability works for quadtrees", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -744,27 +699,22 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, implicitQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability(subtree, subtreeDescription.tileAvailability); - expectContentAvailability( - subtree, - subtreeDescription.contentAvailability - ); - expectChildSubtreeAvailability( - subtree, - subtreeDescription.childSubtreeAvailability - ); - }); + expectTileAvailability(subtree, subtreeDescription.tileAvailability); + expectContentAvailability(subtree, subtreeDescription.contentAvailability); + expectChildSubtreeAvailability( + subtree, + subtreeDescription.childSubtreeAvailability + ); }); - it("computes level offset", function () { + it("computes level offset", async function () { const subtreeDescription = { tileAvailability: { descriptor: "110101111", @@ -787,7 +737,7 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -798,7 +748,7 @@ describe("Scene/ImplicitSubtree", function () { expect(subtree.getLevelOffset(2)).toEqual(9); }); - it("getTileIndex throws for a tile not in the subtree", function () { + it("getTileIndex throws for a tile not in the subtree", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11000", @@ -830,7 +780,7 @@ describe("Scene/ImplicitSubtree", function () { y: 0, }); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -860,7 +810,7 @@ describe("Scene/ImplicitSubtree", function () { }).toThrowError(RuntimeError); }); - it("getTileIndex computes bit index for a tile in the subtree", function () { + it("getTileIndex computes bit index for a tile in the subtree", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11000", @@ -890,7 +840,7 @@ describe("Scene/ImplicitSubtree", function () { x: 0, y: 0, }); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -906,40 +856,38 @@ describe("Scene/ImplicitSubtree", function () { x: 0, y: 0, }); - return subtree.readyPromise.then(function () { - const indexFull = subtree.getTileIndex(implicitCoordinatesFull); - expect(indexFull).toBe(1); - expect(subtree.tileIsAvailableAtIndex(indexFull)).toEqual(true); - expect( - subtree.tileIsAvailableAtCoordinates(implicitCoordinatesFull) - ).toEqual(true); - expect(subtree.contentIsAvailableAtIndex(indexFull)).toEqual(true); - expect( - subtree.contentIsAvailableAtCoordinates(implicitCoordinatesFull) - ).toEqual(true); - - // level offset: 1, morton index: 3, so tile index is 1 + 3 = 4 - const implicitCoordinatesEmpty = new ImplicitTileCoordinates({ - subdivisionScheme: implicitQuadtree.subdivisionScheme, - subtreeLevels: implicitQuadtree.subtreeLevels, - level: 1, - x: 1, - y: 1, - }); - const indexEmpty = subtree.getTileIndex(implicitCoordinatesEmpty); - expect(indexEmpty).toBe(4); - expect(subtree.tileIsAvailableAtIndex(indexEmpty)).toEqual(false); - expect( - subtree.tileIsAvailableAtCoordinates(implicitCoordinatesEmpty) - ).toEqual(false); - expect(subtree.contentIsAvailableAtIndex(indexEmpty)).toEqual(false); - expect( - subtree.contentIsAvailableAtCoordinates(implicitCoordinatesEmpty) - ).toEqual(false); + const indexFull = subtree.getTileIndex(implicitCoordinatesFull); + expect(indexFull).toBe(1); + expect(subtree.tileIsAvailableAtIndex(indexFull)).toEqual(true); + expect( + subtree.tileIsAvailableAtCoordinates(implicitCoordinatesFull) + ).toEqual(true); + expect(subtree.contentIsAvailableAtIndex(indexFull)).toEqual(true); + expect( + subtree.contentIsAvailableAtCoordinates(implicitCoordinatesFull) + ).toEqual(true); + + // level offset: 1, morton index: 3, so tile index is 1 + 3 = 4 + const implicitCoordinatesEmpty = new ImplicitTileCoordinates({ + subdivisionScheme: implicitQuadtree.subdivisionScheme, + subtreeLevels: implicitQuadtree.subtreeLevels, + level: 1, + x: 1, + y: 1, }); + const indexEmpty = subtree.getTileIndex(implicitCoordinatesEmpty); + expect(indexEmpty).toBe(4); + expect(subtree.tileIsAvailableAtIndex(indexEmpty)).toEqual(false); + expect( + subtree.tileIsAvailableAtCoordinates(implicitCoordinatesEmpty) + ).toEqual(false); + expect(subtree.contentIsAvailableAtIndex(indexEmpty)).toEqual(false); + expect( + subtree.contentIsAvailableAtCoordinates(implicitCoordinatesEmpty) + ).toEqual(false); }); - it("getChildSubtreeIndex throws for a tile not in the child subtrees", function () { + it("getChildSubtreeIndex throws for a tile not in the child subtrees", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11000", @@ -969,7 +917,7 @@ describe("Scene/ImplicitSubtree", function () { x: 0, y: 0, }); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -999,7 +947,7 @@ describe("Scene/ImplicitSubtree", function () { }).toThrowError(RuntimeError); }); - it("getChildSubtreeIndex computes bit index for a tile in the subtree", function () { + it("getChildSubtreeIndex computes bit index for a tile in the subtree", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11000", @@ -1029,7 +977,7 @@ describe("Scene/ImplicitSubtree", function () { x: 0, y: 0, }); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -1045,34 +993,32 @@ describe("Scene/ImplicitSubtree", function () { y: 0, }); - return subtree.readyPromise.then(function () { - // morton index is 0, so child subtree index is 0 - const indexFull = subtree.getChildSubtreeIndex(implicitCoordinatesFull); - expect(indexFull).toBe(0); - expect(subtree.childSubtreeIsAvailableAtIndex(indexFull)).toEqual(true); - expect( - subtree.childSubtreeIsAvailableAtCoordinates(implicitCoordinatesFull) - ).toEqual(true); - - const implicitCoordinatesEmpty = new ImplicitTileCoordinates({ - subdivisionScheme: implicitQuadtree.subdivisionScheme, - subtreeLevels: implicitQuadtree.subtreeLevels, - level: 2, - x: 1, - y: 1, - }); + // morton index is 0, so child subtree index is 0 + const indexFull = subtree.getChildSubtreeIndex(implicitCoordinatesFull); + expect(indexFull).toBe(0); + expect(subtree.childSubtreeIsAvailableAtIndex(indexFull)).toEqual(true); + expect( + subtree.childSubtreeIsAvailableAtCoordinates(implicitCoordinatesFull) + ).toEqual(true); - // morton index is 3, so child subtree index is 3 - const indexEmpty = subtree.getChildSubtreeIndex(implicitCoordinatesEmpty); - expect(indexEmpty).toBe(3); - expect(subtree.childSubtreeIsAvailableAtIndex(indexEmpty)).toEqual(false); - expect( - subtree.childSubtreeIsAvailableAtCoordinates(implicitCoordinatesEmpty) - ).toEqual(false); + const implicitCoordinatesEmpty = new ImplicitTileCoordinates({ + subdivisionScheme: implicitQuadtree.subdivisionScheme, + subtreeLevels: implicitQuadtree.subtreeLevels, + level: 2, + x: 1, + y: 1, }); + + // morton index is 3, so child subtree index is 3 + const indexEmpty = subtree.getChildSubtreeIndex(implicitCoordinatesEmpty); + expect(indexEmpty).toBe(3); + expect(subtree.childSubtreeIsAvailableAtIndex(indexEmpty)).toEqual(false); + expect( + subtree.childSubtreeIsAvailableAtCoordinates(implicitCoordinatesEmpty) + ).toEqual(false); }); - it("computes parent Morton index", function () { + it("computes parent Morton index", async function () { const subtreeDescription = { tileAvailability: { descriptor: "110101111", @@ -1095,7 +1041,7 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -1108,7 +1054,7 @@ describe("Scene/ImplicitSubtree", function () { expect(subtree.getParentMortonIndex(341)).toBe(42); }); - it("availability works for octrees", function () { + it("availability works for octrees", async function () { const subtreeDescription = { tileAvailability: { descriptor: "110101111", @@ -1132,27 +1078,22 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, implicitOctree, octreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability(subtree, subtreeDescription.tileAvailability); - expectContentAvailability( - subtree, - subtreeDescription.contentAvailability - ); - expectChildSubtreeAvailability( - subtree, - subtreeDescription.childSubtreeAvailability - ); - }); + expectTileAvailability(subtree, subtreeDescription.tileAvailability); + expectContentAvailability(subtree, subtreeDescription.contentAvailability); + expectChildSubtreeAvailability( + subtree, + subtreeDescription.childSubtreeAvailability + ); }); - it("handles subtree with constant-only data", function () { + it("handles subtree with constant-only data", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -1178,71 +1119,22 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription, constantOnly ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, implicitOctree, octreeCoordinates ); - return subtree.readyPromise.then(function () { - expectTileAvailability(subtree, subtreeDescription.tileAvailability); - expectContentAvailability( - subtree, - subtreeDescription.contentAvailability - ); - expectChildSubtreeAvailability( - subtree, - subtreeDescription.childSubtreeAvailability - ); - }); - }); - - it("rejects ready promise on error", function () { - const error = new Error("simulated error"); - spyOn(Promise, "all").and.callFake(function () { - return Promise.reject(error); - }); - const subtreeDescription = { - tileAvailability: { - descriptor: 1, - lengthBits: 5, - isInternal: true, - }, - contentAvailability: [ - { - descriptor: 1, - lengthBits: 5, - isInternal: true, - }, - ], - childSubtreeAvailability: { - descriptor: 0, - lengthBits: 16, - isInternal: true, - }, - }; - - const results = ImplicitTilingTester.generateSubtreeBuffers( - subtreeDescription - ); - const subtree = new ImplicitSubtree( - subtreeResource, - undefined, - results.subtreeBuffer, - implicitQuadtree, - quadtreeCoordinates + expectTileAvailability(subtree, subtreeDescription.tileAvailability); + expectContentAvailability(subtree, subtreeDescription.contentAvailability); + expectChildSubtreeAvailability( + subtree, + subtreeDescription.childSubtreeAvailability ); - return subtree.readyPromise - .then(function () { - fail(); - }) - .catch(function (error) { - expect(error).toEqual(error); - }); }); - it("destroys", function () { + it("destroys", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11010", @@ -1265,24 +1157,24 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - spyOn(ResourceCache, "load").and.callFake(fakeLoad(results.externalBuffer)); + spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) + ); const unload = spyOn(ResourceCache, "unload"); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, implicitQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - const bufferLoader = subtree._bufferLoader; - expect(bufferLoader).toBeDefined(); + const bufferLoader = subtree._bufferLoader; + expect(bufferLoader).toBeDefined(); - expect(subtree.isDestroyed()).toBe(false); - subtree.destroy(); - expect(subtree.isDestroyed()).toBe(true); - expect(unload).toHaveBeenCalled(); - }); + expect(subtree.isDestroyed()).toBe(false); + subtree.destroy(); + expect(subtree.isDestroyed()).toBe(true); + expect(unload).toHaveBeenCalled(); }); describe("multiple contents", function () { @@ -1328,7 +1220,7 @@ describe("Scene/ImplicitSubtree", function () { }); }); - it("contentIsAvailableAtIndex throws for out-of-bounds contentIndex", function () { + it("contentIsAvailableAtIndex throws for out-of-bounds contentIndex", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -1356,7 +1248,7 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -1365,14 +1257,12 @@ describe("Scene/ImplicitSubtree", function () { ); const outOfBounds = 100; - return subtree.readyPromise.then(function () { - expect(function () { - subtree.contentIsAvailableAtIndex(0, outOfBounds); - }).toThrowDeveloperError(); - }); + expect(function () { + subtree.contentIsAvailableAtIndex(0, outOfBounds); + }).toThrowDeveloperError(); }); - it("contentIsAvailableAtIndex works for multiple contents", function () { + it("contentIsAvailableAtIndex works for multiple contents", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -1401,11 +1291,11 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -1413,17 +1303,15 @@ describe("Scene/ImplicitSubtree", function () { multipleContentsCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).toHaveBeenCalled(); - expectTileAvailability(subtree, subtreeDescription.tileAvailability); - expectContentAvailability( - subtree, - subtreeDescription.contentAvailability - ); - }); + expect(fetchExternal).toHaveBeenCalled(); + expectTileAvailability(subtree, subtreeDescription.tileAvailability); + expectContentAvailability( + subtree, + subtreeDescription.contentAvailability + ); }); - it("contentIsAvailableAtIndex works for multiple contents (legacy)", function () { + it("contentIsAvailableAtIndex works for multiple contents (legacy)", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -1453,11 +1341,11 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -1465,14 +1353,12 @@ describe("Scene/ImplicitSubtree", function () { multipleContentsCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).toHaveBeenCalled(); - expectTileAvailability(subtree, subtreeDescription.tileAvailability); - expectContentAvailability( - subtree, - subtreeDescription.contentAvailability - ); - }); + expect(fetchExternal).toHaveBeenCalled(); + expectTileAvailability(subtree, subtreeDescription.tileAvailability); + expectContentAvailability( + subtree, + subtreeDescription.contentAvailability + ); }); }); @@ -1660,7 +1546,7 @@ describe("Scene/ImplicitSubtree", function () { ); }); - it("creates subtree metadata from JSON", function () { + it("creates subtree metadata from JSON", async function () { const metadataSubtreeJson = { tileAvailability: { constant: 1, @@ -1680,7 +1566,7 @@ describe("Scene/ImplicitSubtree", function () { }, }; - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeJsonResource, metadataSubtreeJson, undefined, @@ -1688,19 +1574,17 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - const metadata = subtree.metadata; - expect(metadata).toBeDefined(); + const metadata = subtree.metadata; + expect(metadata).toBeDefined(); - expect(metadata.hasProperty("author")).toBe(true); - expect(metadata.hasProperty("credits")).toBe(true); + expect(metadata.hasProperty("author")).toBe(true); + expect(metadata.hasProperty("credits")).toBe(true); - expect(metadata.getProperty("author")).toEqual("Cesium"); - expect(metadata.getProperty("credits")).toEqual(["A", "B", "C"]); - }); + expect(metadata.getProperty("author")).toEqual("Cesium"); + expect(metadata.getProperty("credits")).toEqual(["A", "B", "C"]); }); - it("creates a metadata table from internal metadata for tiles", function () { + it("creates a metadata table from internal metadata for tiles", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -1729,10 +1613,10 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -1740,25 +1624,23 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); + expect(fetchExternal).not.toHaveBeenCalled(); - const metadataTable = subtree.tileMetadataTable; - expect(metadataTable).toBeDefined(); - expect(metadataTable.count).toBe(5); + const metadataTable = subtree.tileMetadataTable; + expect(metadataTable).toBeDefined(); + expect(metadataTable.count).toBe(5); - for (let i = 0; i < buildingCounts.length; i++) { - expect(metadataTable.getProperty(i, "highlightColor")).toEqual( - Cartesian3.unpack(highlightColors[i]) - ); - expect(metadataTable.getProperty(i, "buildingCount")).toBe( - buildingCounts[i] - ); - } - }); + for (let i = 0; i < buildingCounts.length; i++) { + expect(metadataTable.getProperty(i, "highlightColor")).toEqual( + Cartesian3.unpack(highlightColors[i]) + ); + expect(metadataTable.getProperty(i, "buildingCount")).toBe( + buildingCounts[i] + ); + } }); - it("creates a metadata table from external metadata for tiles", function () { + it("creates a metadata table from external metadata for tiles", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -1787,10 +1669,10 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -1798,25 +1680,23 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).toHaveBeenCalled(); + expect(fetchExternal).toHaveBeenCalled(); - const metadataTable = subtree.tileMetadataTable; - expect(metadataTable).toBeDefined(); - expect(metadataTable.count).toBe(5); + const metadataTable = subtree.tileMetadataTable; + expect(metadataTable).toBeDefined(); + expect(metadataTable.count).toBe(5); - for (let i = 0; i < buildingCounts.length; i++) { - expect(metadataTable.getProperty(i, "highlightColor")).toEqual( - Cartesian3.unpack(highlightColors[i]) - ); - expect(metadataTable.getProperty(i, "buildingCount")).toBe( - buildingCounts[i] - ); - } - }); + for (let i = 0; i < buildingCounts.length; i++) { + expect(metadataTable.getProperty(i, "highlightColor")).toEqual( + Cartesian3.unpack(highlightColors[i]) + ); + expect(metadataTable.getProperty(i, "buildingCount")).toBe( + buildingCounts[i] + ); + } }); - it("creates a metadata table from internal metadata for single content", function () { + it("creates a metadata table from internal metadata for single content", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -1845,10 +1725,10 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -1856,28 +1736,26 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); - const metadataTables = subtree.contentMetadataTables; - expect(metadataTables).toBeDefined(); - expect(metadataTables.length).toBe(1); - - const metadataTable = metadataTables[0]; - expect(metadataTable).toBeDefined(); - expect(metadataTable.count).toBe(3); - - for (let i = 0; i < buildingHeights.length; i++) { - expect(metadataTable.getProperty(i, "height")).toEqual( - buildingHeights[i] - ); - expect(metadataTable.getProperty(i, "buildingType")).toBe( - buildingTypes[i] - ); - } - }); + expect(fetchExternal).not.toHaveBeenCalled(); + const metadataTables = subtree.contentMetadataTables; + expect(metadataTables).toBeDefined(); + expect(metadataTables.length).toBe(1); + + const metadataTable = metadataTables[0]; + expect(metadataTable).toBeDefined(); + expect(metadataTable.count).toBe(3); + + for (let i = 0; i < buildingHeights.length; i++) { + expect(metadataTable.getProperty(i, "height")).toEqual( + buildingHeights[i] + ); + expect(metadataTable.getProperty(i, "buildingType")).toBe( + buildingTypes[i] + ); + } }); - it("creates a metadata table from external metadata for single content", function () { + it("creates a metadata table from external metadata for single content", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -1906,10 +1784,10 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -1917,28 +1795,26 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).toHaveBeenCalled(); - const metadataTables = subtree.contentMetadataTables; - expect(metadataTables).toBeDefined(); - expect(metadataTables.length).toBe(1); - - const metadataTable = metadataTables[0]; - expect(metadataTable).toBeDefined(); - expect(metadataTable.count).toBe(3); - - for (let i = 0; i < buildingHeights.length; i++) { - expect(metadataTable.getProperty(i, "height")).toEqual( - buildingHeights[i] - ); - expect(metadataTable.getProperty(i, "buildingType")).toBe( - buildingTypes[i] - ); - } - }); + expect(fetchExternal).toHaveBeenCalled(); + const metadataTables = subtree.contentMetadataTables; + expect(metadataTables).toBeDefined(); + expect(metadataTables.length).toBe(1); + + const metadataTable = metadataTables[0]; + expect(metadataTable).toBeDefined(); + expect(metadataTable.count).toBe(3); + + for (let i = 0; i < buildingHeights.length; i++) { + expect(metadataTable.getProperty(i, "height")).toEqual( + buildingHeights[i] + ); + expect(metadataTable.getProperty(i, "buildingType")).toBe( + buildingTypes[i] + ); + } }); - it("creates a metadata table from internal metadata for multiple contents", function () { + it("creates a metadata table from internal metadata for multiple contents", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -1972,10 +1848,10 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -1983,41 +1859,39 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); - const metadataTables = subtree.contentMetadataTables; - expect(metadataTables).toBeDefined(); - expect(metadataTables.length).toBe(2); - - const buildingMetadataTable = metadataTables[0]; - expect(buildingMetadataTable).toBeDefined(); - expect(buildingMetadataTable.count).toBe(3); - - for (let i = 0; i < buildingHeights.length; i++) { - expect(buildingMetadataTable.getProperty(i, "height")).toEqual( - buildingHeights[i] - ); - expect(buildingMetadataTable.getProperty(i, "buildingType")).toBe( - buildingTypes[i] - ); - } - - const treeMetadataTable = metadataTables[1]; - expect(treeMetadataTable).toBeDefined(); - expect(treeMetadataTable.count).toBe(4); - - for (let i = 0; i < treeHeights.length; i++) { - expect(treeMetadataTable.getProperty(i, "height")).toEqual( - treeHeights[i] - ); - expect(treeMetadataTable.getProperty(i, "species")).toBe( - treeSpecies[i] - ); - } - }); + expect(fetchExternal).not.toHaveBeenCalled(); + const metadataTables = subtree.contentMetadataTables; + expect(metadataTables).toBeDefined(); + expect(metadataTables.length).toBe(2); + + const buildingMetadataTable = metadataTables[0]; + expect(buildingMetadataTable).toBeDefined(); + expect(buildingMetadataTable.count).toBe(3); + + for (let i = 0; i < buildingHeights.length; i++) { + expect(buildingMetadataTable.getProperty(i, "height")).toEqual( + buildingHeights[i] + ); + expect(buildingMetadataTable.getProperty(i, "buildingType")).toBe( + buildingTypes[i] + ); + } + + const treeMetadataTable = metadataTables[1]; + expect(treeMetadataTable).toBeDefined(); + expect(treeMetadataTable.count).toBe(4); + + for (let i = 0; i < treeHeights.length; i++) { + expect(treeMetadataTable.getProperty(i, "height")).toEqual( + treeHeights[i] + ); + expect(treeMetadataTable.getProperty(i, "species")).toBe( + treeSpecies[i] + ); + } }); - it("creates a metadata table from external metadata for multiple contents", function () { + it("creates a metadata table from external metadata for multiple contents", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -2051,10 +1925,10 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2062,41 +1936,39 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).toHaveBeenCalled(); - const metadataTables = subtree.contentMetadataTables; - expect(metadataTables).toBeDefined(); - expect(metadataTables.length).toBe(2); - - const buildingMetadataTable = metadataTables[0]; - expect(buildingMetadataTable).toBeDefined(); - expect(buildingMetadataTable.count).toBe(3); - - for (let i = 0; i < buildingHeights.length; i++) { - expect(buildingMetadataTable.getProperty(i, "height")).toEqual( - buildingHeights[i] - ); - expect(buildingMetadataTable.getProperty(i, "buildingType")).toBe( - buildingTypes[i] - ); - } - - const treeMetadataTable = metadataTables[1]; - expect(treeMetadataTable).toBeDefined(); - expect(treeMetadataTable.count).toBe(4); - - for (let i = 0; i < treeHeights.length; i++) { - expect(treeMetadataTable.getProperty(i, "height")).toEqual( - treeHeights[i] - ); - expect(treeMetadataTable.getProperty(i, "species")).toBe( - treeSpecies[i] - ); - } - }); + expect(fetchExternal).toHaveBeenCalled(); + const metadataTables = subtree.contentMetadataTables; + expect(metadataTables).toBeDefined(); + expect(metadataTables.length).toBe(2); + + const buildingMetadataTable = metadataTables[0]; + expect(buildingMetadataTable).toBeDefined(); + expect(buildingMetadataTable.count).toBe(3); + + for (let i = 0; i < buildingHeights.length; i++) { + expect(buildingMetadataTable.getProperty(i, "height")).toEqual( + buildingHeights[i] + ); + expect(buildingMetadataTable.getProperty(i, "buildingType")).toBe( + buildingTypes[i] + ); + } + + const treeMetadataTable = metadataTables[1]; + expect(treeMetadataTable).toBeDefined(); + expect(treeMetadataTable.count).toBe(4); + + for (let i = 0; i < treeHeights.length; i++) { + expect(treeMetadataTable.getProperty(i, "height")).toEqual( + treeHeights[i] + ); + expect(treeMetadataTable.getProperty(i, "species")).toBe( + treeSpecies[i] + ); + } }); - it("creates tile metadata view", function () { + it("creates tile metadata view", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -2126,11 +1998,11 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2138,20 +2010,18 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); - const metadataView = subtree.getTileMetadataView(quadtreeCoordinates); - expect(metadataView).toBeDefined(); - expect(metadataView.getProperty("highlightColor")).toEqual( - Cartesian3.unpack(highlightColors[0]) - ); - expect(metadataView.getProperty("buildingCount")).toEqual( - buildingCounts[0] - ); - }); + expect(fetchExternal).not.toHaveBeenCalled(); + const metadataView = subtree.getTileMetadataView(quadtreeCoordinates); + expect(metadataView).toBeDefined(); + expect(metadataView.getProperty("highlightColor")).toEqual( + Cartesian3.unpack(highlightColors[0]) + ); + expect(metadataView.getProperty("buildingCount")).toEqual( + buildingCounts[0] + ); }); - it("returns undefined tile metadata view for invalid coordinates", function () { + it("returns undefined tile metadata view for invalid coordinates", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11000", @@ -2181,11 +2051,11 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2193,23 +2063,21 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); - const coordinates = new ImplicitTileCoordinates({ - subdivisionScheme: implicitQuadtree.subdivisionScheme, - subtreeLevels: implicitQuadtree.subtreeLevels, - level: 1, - x: 1, - y: 1, - }); + expect(fetchExternal).not.toHaveBeenCalled(); + const coordinates = new ImplicitTileCoordinates({ + subdivisionScheme: implicitQuadtree.subdivisionScheme, + subtreeLevels: implicitQuadtree.subtreeLevels, + level: 1, + x: 1, + y: 1, + }); - const metadataView = subtree.getTileMetadataView(coordinates); + const metadataView = subtree.getTileMetadataView(coordinates); - expect(metadataView).not.toBeDefined(); - }); + expect(metadataView).not.toBeDefined(); }); - it("creates content metadata view", function () { + it("creates content metadata view", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -2239,11 +2107,11 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2251,21 +2119,19 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); - const metadataView = subtree.getContentMetadataView( - quadtreeCoordinates, - 0 - ); - expect(metadataView).toBeDefined(); - expect(metadataView.getProperty("height")).toEqual(buildingHeights[0]); - expect(metadataView.getProperty("buildingType")).toEqual( - buildingTypes[0] - ); - }); + expect(fetchExternal).not.toHaveBeenCalled(); + const metadataView = subtree.getContentMetadataView( + quadtreeCoordinates, + 0 + ); + expect(metadataView).toBeDefined(); + expect(metadataView.getProperty("height")).toEqual(buildingHeights[0]); + expect(metadataView.getProperty("buildingType")).toEqual( + buildingTypes[0] + ); }); - it("returns undefined content metadata view for invalid coordinates", function () { + it("returns undefined content metadata view for invalid coordinates", async function () { const subtreeDescription = { tileAvailability: { descriptor: "11000", @@ -2295,11 +2161,11 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2307,23 +2173,21 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); - const coordinates = new ImplicitTileCoordinates({ - subdivisionScheme: implicitQuadtree.subdivisionScheme, - subtreeLevels: implicitQuadtree.subtreeLevels, - level: 1, - x: 1, - y: 1, - }); + expect(fetchExternal).not.toHaveBeenCalled(); + const coordinates = new ImplicitTileCoordinates({ + subdivisionScheme: implicitQuadtree.subdivisionScheme, + subtreeLevels: implicitQuadtree.subtreeLevels, + level: 1, + x: 1, + y: 1, + }); - const metadataView = subtree.getContentMetadataView(coordinates, 0); + const metadataView = subtree.getContentMetadataView(coordinates, 0); - expect(metadataView).not.toBeDefined(); - }); + expect(metadataView).not.toBeDefined(); }); - it("creates content metadata view for multiple contents", function () { + it("creates content metadata view for multiple contents", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -2358,11 +2222,11 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2370,36 +2234,34 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); - const coordinates = new ImplicitTileCoordinates({ - subdivisionScheme: implicitQuadtree.subdivisionScheme, - subtreeLevels: implicitQuadtree.subtreeLevels, - level: 1, - x: 0, - y: 1, - }); + expect(fetchExternal).not.toHaveBeenCalled(); + const coordinates = new ImplicitTileCoordinates({ + subdivisionScheme: implicitQuadtree.subdivisionScheme, + subtreeLevels: implicitQuadtree.subtreeLevels, + level: 1, + x: 0, + y: 1, + }); - const buildingMetadataView = subtree.getContentMetadataView( - coordinates, - 0 - ); - expect(buildingMetadataView).toBeDefined(); - expect(buildingMetadataView.getProperty("height")).toEqual( - buildingHeights[2] - ); - expect(buildingMetadataView.getProperty("buildingType")).toEqual( - buildingTypes[2] - ); + const buildingMetadataView = subtree.getContentMetadataView( + coordinates, + 0 + ); + expect(buildingMetadataView).toBeDefined(); + expect(buildingMetadataView.getProperty("height")).toEqual( + buildingHeights[2] + ); + expect(buildingMetadataView.getProperty("buildingType")).toEqual( + buildingTypes[2] + ); - const treeMetadataView = subtree.getContentMetadataView(coordinates, 1); - expect(treeMetadataView).toBeDefined(); - expect(treeMetadataView.getProperty("height")).toEqual(treeHeights[2]); - expect(treeMetadataView.getProperty("species")).toEqual(treeSpecies[2]); - }); + const treeMetadataView = subtree.getContentMetadataView(coordinates, 1); + expect(treeMetadataView).toBeDefined(); + expect(treeMetadataView.getProperty("height")).toEqual(treeHeights[2]); + expect(treeMetadataView.getProperty("species")).toEqual(treeSpecies[2]); }); - it("returns undefined content metadata view for invalid coordinates and mutliple contents", function () { + it("returns undefined content metadata view for invalid coordinates and multiple contents", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -2434,11 +2296,11 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2446,31 +2308,29 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); - const coordinates = new ImplicitTileCoordinates({ - subdivisionScheme: implicitQuadtree.subdivisionScheme, - subtreeLevels: implicitQuadtree.subtreeLevels, - level: 1, - x: 1, - y: 1, - }); + expect(fetchExternal).not.toHaveBeenCalled(); + const coordinates = new ImplicitTileCoordinates({ + subdivisionScheme: implicitQuadtree.subdivisionScheme, + subtreeLevels: implicitQuadtree.subtreeLevels, + level: 1, + x: 1, + y: 1, + }); - const buildingMetadataView = subtree.getContentMetadataView( - coordinates, - 0 - ); - expect(buildingMetadataView).not.toBeDefined(); + const buildingMetadataView = subtree.getContentMetadataView( + coordinates, + 0 + ); + expect(buildingMetadataView).not.toBeDefined(); - const treeMetadataView = subtree.getContentMetadataView( - quadtreeCoordinates, - 1 - ); - expect(treeMetadataView).not.toBeDefined(); - }); + const treeMetadataView = subtree.getContentMetadataView( + quadtreeCoordinates, + 1 + ); + expect(treeMetadataView).not.toBeDefined(); }); - it("handles 3DTILES_metadata extension for backwards compatibility", function () { + it("handles 3DTILES_metadata extension for backwards compatibility", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -2500,10 +2360,10 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2511,28 +2371,26 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); - const metadataTables = subtree.contentMetadataTables; - expect(metadataTables).toBeDefined(); - expect(metadataTables.length).toBe(1); - - const metadataTable = metadataTables[0]; - expect(metadataTable).toBeDefined(); - expect(metadataTable.count).toBe(3); - - for (let i = 0; i < buildingHeights.length; i++) { - expect(metadataTable.getProperty(i, "height")).toEqual( - buildingHeights[i] - ); - expect(metadataTable.getProperty(i, "buildingType")).toBe( - buildingTypes[i] - ); - } - }); + expect(fetchExternal).not.toHaveBeenCalled(); + const metadataTables = subtree.contentMetadataTables; + expect(metadataTables).toBeDefined(); + expect(metadataTables.length).toBe(1); + + const metadataTable = metadataTables[0]; + expect(metadataTable).toBeDefined(); + expect(metadataTable.count).toBe(3); + + for (let i = 0; i < buildingHeights.length; i++) { + expect(metadataTable.getProperty(i, "height")).toEqual( + buildingHeights[i] + ); + expect(metadataTable.getProperty(i, "buildingType")).toBe( + buildingTypes[i] + ); + } }); - it("works correctly if availableCount is undefined", function () { + it("works correctly if availableCount is undefined", async function () { const subtreeDescription = { tileAvailability: { descriptor: 1, @@ -2562,10 +2420,10 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const fetchExternal = spyOn(ResourceCache, "load").and.callFake( - fakeLoad(results.externalBuffer) + const fetchExternal = spyOn(ResourceCache, "get").and.callFake( + fakeResourceLoader(results.externalBuffer) ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2573,25 +2431,23 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(fetchExternal).not.toHaveBeenCalled(); + expect(fetchExternal).not.toHaveBeenCalled(); - const metadataTable = subtree.tileMetadataTable; - expect(metadataTable).toBeDefined(); - expect(metadataTable.count).toBe(5); + const metadataTable = subtree.tileMetadataTable; + expect(metadataTable).toBeDefined(); + expect(metadataTable.count).toBe(5); - for (let i = 0; i < buildingCounts.length; i++) { - expect(metadataTable.getProperty(i, "highlightColor")).toEqual( - Cartesian3.unpack(highlightColors[i]) - ); - expect(metadataTable.getProperty(i, "buildingCount")).toBe( - buildingCounts[i] - ); - } - }); + for (let i = 0; i < buildingCounts.length; i++) { + expect(metadataTable.getProperty(i, "highlightColor")).toEqual( + Cartesian3.unpack(highlightColors[i]) + ); + expect(metadataTable.getProperty(i, "buildingCount")).toBe( + buildingCounts[i] + ); + } }); - it("handles unavailable tiles correctly", function () { + it("handles unavailable tiles correctly", async function () { const highlightColors = [ [255, 0, 0], [255, 255, 0], @@ -2641,34 +2497,30 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, tileMetadataQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - expect(subtree._tileJumpBuffer).toEqual( - new Uint8Array([0, 0, 0, 1, 2]) - ); + expect(subtree._tileJumpBuffer).toEqual(new Uint8Array([0, 0, 0, 1, 2])); - const metadataTable = subtree.tileMetadataTable; - expect(metadataTable).toBeDefined(); - expect(metadataTable.count).toBe(3); - - for (let i = 0; i < buildingCounts.length; i++) { - expect(metadataTable.getProperty(i, "highlightColor")).toEqual( - Cartesian3.unpack(highlightColors[i]) - ); - expect(metadataTable.getProperty(i, "buildingCount")).toBe( - buildingCounts[i] - ); - } - }); + const metadataTable = subtree.tileMetadataTable; + expect(metadataTable).toBeDefined(); + expect(metadataTable.count).toBe(3); + + for (let i = 0; i < buildingCounts.length; i++) { + expect(metadataTable.getProperty(i, "highlightColor")).toEqual( + Cartesian3.unpack(highlightColors[i]) + ); + expect(metadataTable.getProperty(i, "buildingCount")).toBe( + buildingCounts[i] + ); + } }); - it("handles unavailable content correctly", function () { + it("handles unavailable content correctly", async function () { const buildingHeightsTruncated = buildingHeights.slice(0, 2); const buildingTypesTruncated = buildingTypes.slice(0, 2); @@ -2701,7 +2553,7 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2709,30 +2561,28 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - const jumpBuffer = subtree._contentJumpBuffers[0]; - expect(jumpBuffer).toEqual(new Uint8Array([0, 0, 0, 1, 0])); + const jumpBuffer = subtree._contentJumpBuffers[0]; + expect(jumpBuffer).toEqual(new Uint8Array([0, 0, 0, 1, 0])); - const metadataTables = subtree.contentMetadataTables; - expect(metadataTables).toBeDefined(); - expect(metadataTables.length).toBe(1); + const metadataTables = subtree.contentMetadataTables; + expect(metadataTables).toBeDefined(); + expect(metadataTables.length).toBe(1); - const metadataTable = metadataTables[0]; - expect(metadataTable).toBeDefined(); - expect(metadataTable.count).toBe(2); + const metadataTable = metadataTables[0]; + expect(metadataTable).toBeDefined(); + expect(metadataTable.count).toBe(2); - for (let i = 0; i < buildingHeightsTruncated.length; i++) { - expect(metadataTable.getProperty(i, "height")).toEqual( - buildingHeightsTruncated[i] - ); - expect(metadataTable.getProperty(i, "buildingType")).toBe( - buildingTypesTruncated[i] - ); - } - }); + for (let i = 0; i < buildingHeightsTruncated.length; i++) { + expect(metadataTable.getProperty(i, "height")).toEqual( + buildingHeightsTruncated[i] + ); + expect(metadataTable.getProperty(i, "buildingType")).toBe( + buildingTypesTruncated[i] + ); + } }); - it("handles unavailable multiple contents correctly", function () { + it("handles unavailable multiple contents correctly", async function () { const buildingHeightsTruncated = buildingHeights.slice(0, 1); const buildingTypesTruncated = buildingTypes.slice(0, 1); @@ -2773,7 +2623,7 @@ describe("Scene/ImplicitSubtree", function () { subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, @@ -2781,46 +2631,44 @@ describe("Scene/ImplicitSubtree", function () { quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - const buildingJumpBuffer = subtree._contentJumpBuffers[0]; - expect(buildingJumpBuffer).toEqual(new Uint8Array([0, 0, 0, 0, 0])); - - const treeJumpBuffer = subtree._contentJumpBuffers[1]; - expect(treeJumpBuffer).toEqual(new Uint8Array([0, 0, 0, 0, 1])); - - const metadataTables = subtree.contentMetadataTables; - expect(metadataTables).toBeDefined(); - expect(metadataTables.length).toBe(2); - - const buildingMetadataTable = metadataTables[0]; - expect(buildingMetadataTable).toBeDefined(); - expect(buildingMetadataTable.count).toBe(1); - - for (let i = 0; i < buildingHeightsTruncated.length; i++) { - expect(buildingMetadataTable.getProperty(i, "height")).toEqual( - buildingHeightsTruncated[i] - ); - expect(buildingMetadataTable.getProperty(i, "buildingType")).toBe( - buildingTypesTruncated[i] - ); - } - - const treeMetadataTable = metadataTables[1]; - expect(treeMetadataTable).toBeDefined(); - expect(treeMetadataTable.count).toBe(2); - - for (let i = 0; i < treeHeightsTruncated.length; i++) { - expect(treeMetadataTable.getProperty(i, "height")).toEqual( - treeHeightsTruncated[i] - ); - expect(treeMetadataTable.getProperty(i, "species")).toBe( - treeSpeciesTruncated[i] - ); - } - }); + const buildingJumpBuffer = subtree._contentJumpBuffers[0]; + expect(buildingJumpBuffer).toEqual(new Uint8Array([0, 0, 0, 0, 0])); + + const treeJumpBuffer = subtree._contentJumpBuffers[1]; + expect(treeJumpBuffer).toEqual(new Uint8Array([0, 0, 0, 0, 1])); + + const metadataTables = subtree.contentMetadataTables; + expect(metadataTables).toBeDefined(); + expect(metadataTables.length).toBe(2); + + const buildingMetadataTable = metadataTables[0]; + expect(buildingMetadataTable).toBeDefined(); + expect(buildingMetadataTable.count).toBe(1); + + for (let i = 0; i < buildingHeightsTruncated.length; i++) { + expect(buildingMetadataTable.getProperty(i, "height")).toEqual( + buildingHeightsTruncated[i] + ); + expect(buildingMetadataTable.getProperty(i, "buildingType")).toBe( + buildingTypesTruncated[i] + ); + } + + const treeMetadataTable = metadataTables[1]; + expect(treeMetadataTable).toBeDefined(); + expect(treeMetadataTable.count).toBe(2); + + for (let i = 0; i < treeHeightsTruncated.length; i++) { + expect(treeMetadataTable.getProperty(i, "height")).toEqual( + treeHeightsTruncated[i] + ); + expect(treeMetadataTable.getProperty(i, "species")).toBe( + treeSpeciesTruncated[i] + ); + } }); - it("handles metadata with string and array offsets", function () { + it("handles metadata with string and array offsets", async function () { if (!MetadataTester.isSupported()) { return; } @@ -2906,33 +2754,31 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, arrayQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - const metadataTable = subtree.tileMetadataTable; - expect(metadataTable).toBeDefined(); - expect(metadataTable.count).toBe(5); - - for (let i = 0; i < buildingCounts.length; i++) { - expect(metadataTable.getProperty(i, "stringProperty")).toBe( - stringValues[i] - ); - expect(metadataTable.getProperty(i, "arrayProperty")).toEqual( - arrayValues[i] - ); - expect(metadataTable.getProperty(i, "arrayOfStringProperty")).toEqual( - stringArrayValues[i] - ); - } - }); + const metadataTable = subtree.tileMetadataTable; + expect(metadataTable).toBeDefined(); + expect(metadataTable.count).toBe(5); + + for (let i = 0; i < buildingCounts.length; i++) { + expect(metadataTable.getProperty(i, "stringProperty")).toBe( + stringValues[i] + ); + expect(metadataTable.getProperty(i, "arrayProperty")).toEqual( + arrayValues[i] + ); + expect(metadataTable.getProperty(i, "arrayOfStringProperty")).toEqual( + stringArrayValues[i] + ); + } }); - it("handles legacy 3DTILES_metadata schema correctly for arrays and strings", function () { + it("handles legacy 3DTILES_metadata schema correctly for arrays and strings", async function () { if (!MetadataTester.isSupported()) { return; } @@ -3019,30 +2865,28 @@ describe("Scene/ImplicitSubtree", function () { const results = ImplicitTilingTester.generateSubtreeBuffers( subtreeDescription ); - const subtree = new ImplicitSubtree( + const subtree = await ImplicitSubtree.fromSubtreeJson( subtreeResource, undefined, results.subtreeBuffer, arrayQuadtree, quadtreeCoordinates ); - return subtree.readyPromise.then(function () { - const metadataTable = subtree.tileMetadataTable; - expect(metadataTable).toBeDefined(); - expect(metadataTable.count).toBe(5); - - for (let i = 0; i < buildingCounts.length; i++) { - expect(metadataTable.getProperty(i, "stringProperty")).toBe( - stringValues[i] - ); - expect(metadataTable.getProperty(i, "arrayProperty")).toEqual( - arrayValues[i] - ); - expect(metadataTable.getProperty(i, "arrayOfStringProperty")).toEqual( - stringArrayValues[i] - ); - } - }); + const metadataTable = subtree.tileMetadataTable; + expect(metadataTable).toBeDefined(); + expect(metadataTable.count).toBe(5); + + for (let i = 0; i < buildingCounts.length; i++) { + expect(metadataTable.getProperty(i, "stringProperty")).toBe( + stringValues[i] + ); + expect(metadataTable.getProperty(i, "arrayProperty")).toEqual( + arrayValues[i] + ); + expect(metadataTable.getProperty(i, "arrayOfStringProperty")).toEqual( + stringArrayValues[i] + ); + } }); }); }); diff --git a/packages/engine/Specs/Scene/Model/B3dmLoaderSpec.js b/packages/engine/Specs/Scene/Model/B3dmLoaderSpec.js index 2db292a8c4ea..7933375ca4b1 100644 --- a/packages/engine/Specs/Scene/Model/B3dmLoaderSpec.js +++ b/packages/engine/Specs/Scene/Model/B3dmLoaderSpec.js @@ -52,15 +52,15 @@ describe( ResourceCache.clearForSpecs(); }); - function loadB3dmArrayBuffer(resource, arrayBuffer) { + async function loadB3dmArrayBuffer(resource, arrayBuffer) { const loader = new B3dmLoader({ b3dmResource: resource, arrayBuffer: arrayBuffer, }); b3dmLoaders.push(loader); - loader.load(); - - return waitForLoaderProcess(loader, scene); + await loader.load(); + await waitForLoaderProcess(loader, scene); + return loader; } function loadB3dm(b3dmPath) { @@ -73,11 +73,11 @@ describe( }); } - function expectLoadError(arrayBuffer) { + async function expectLoadError(arrayBuffer) { const resource = new Resource("http://example.com/test.b3dm"); - expect(function () { - return loadB3dmArrayBuffer(resource, arrayBuffer); - }).toThrowError(RuntimeError); + await expectAsync( + loadB3dmArrayBuffer(resource, arrayBuffer) + ).toBeRejectedWithError(RuntimeError); } it("releases array buffer when finished loading", function () { @@ -154,17 +154,17 @@ describe( }); }); - it("throws with invalid version", function () { + it("throws with invalid version", async function () { const arrayBuffer = Cesium3DTilesTester.generateBatchedTileBuffer({ version: 2, }); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); - it("throws with empty gltf", function () { + it("throws with empty gltf", async function () { // Expect to throw DeveloperError in Model due to invalid gltf magic const arrayBuffer = Cesium3DTilesTester.generateBatchedTileBuffer(); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); it("destroys b3dm loader", function () { diff --git a/packages/engine/Specs/Scene/Model/DequantizationPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/DequantizationPipelineStageSpec.js index df23fe2f23cc..c8eed6be5bd5 100644 --- a/packages/engine/Specs/Scene/Model/DequantizationPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/DequantizationPipelineStageSpec.js @@ -61,12 +61,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockRenderResources() { diff --git a/packages/engine/Specs/Scene/Model/FeatureIdPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/FeatureIdPipelineStageSpec.js index e73c69036a81..13cd3feaf75e 100644 --- a/packages/engine/Specs/Scene/Model/FeatureIdPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/FeatureIdPipelineStageSpec.js @@ -68,12 +68,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockRenderResources(node) { diff --git a/packages/engine/Specs/Scene/Model/GeoJsonLoaderSpec.js b/packages/engine/Specs/Scene/Model/GeoJsonLoaderSpec.js index 0d399bcfc58c..adb5ca1b8bd1 100644 --- a/packages/engine/Specs/Scene/Model/GeoJsonLoaderSpec.js +++ b/packages/engine/Specs/Scene/Model/GeoJsonLoaderSpec.js @@ -55,18 +55,19 @@ describe( ResourceCache.clearForSpecs(); }); - function loadGeoJson(geoJsonPath) { - return Resource.fetchJson({ + async function loadGeoJson(geoJsonPath) { + const json = await Resource.fetchJson({ url: geoJsonPath, - }).then(function (json) { - const loader = new GeoJsonLoader({ - geoJson: json, - }); - geoJsonLoaders.push(loader); - loader.load(); - - return waitForLoaderProcess(loader, scene); }); + + const loader = new GeoJsonLoader({ + geoJson: json, + }); + + geoJsonLoaders.push(loader); + await loader.load(); + await waitForLoaderProcess(loader, scene); + return loader; } function getAttribute(attributes, semantic, setIndex) { diff --git a/packages/engine/Specs/Scene/Model/GeometryPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/GeometryPipelineStageSpec.js index 1fea0d0f0600..cc34a4795f4b 100644 --- a/packages/engine/Specs/Scene/Model/GeometryPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/GeometryPipelineStageSpec.js @@ -132,12 +132,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockRenderResources(primitive) { diff --git a/packages/engine/Specs/Scene/Model/I3dmLoaderSpec.js b/packages/engine/Specs/Scene/Model/I3dmLoaderSpec.js index 3a565948bdbb..b3243f50772e 100644 --- a/packages/engine/Specs/Scene/Model/I3dmLoaderSpec.js +++ b/packages/engine/Specs/Scene/Model/I3dmLoaderSpec.js @@ -68,37 +68,37 @@ describe( ResourceCache.clearForSpecs(); }); - function loadI3dm(path) { + async function loadI3dm(path) { const resource = Resource.createIfNeeded(path); - return Resource.fetchArrayBuffer({ + const arrayBuffer = await Resource.fetchArrayBuffer({ url: path, - }).then(function (arrayBuffer) { - const loader = new I3dmLoader({ - i3dmResource: resource, - arrayBuffer: arrayBuffer, - }); - i3dmLoaders.push(loader); - loader.load(); - - return waitForLoaderProcess(loader, scene); }); + const loader = new I3dmLoader({ + i3dmResource: resource, + arrayBuffer: arrayBuffer, + }); + i3dmLoaders.push(loader); + await loader.load(); + await waitForLoaderProcess(loader, scene); + return loader; } - function expectLoadError(arrayBuffer) { - expect(function () { - const resource = Resource.createIfNeeded( - "http://example.com/content.i3dm" - ); - const loader = new I3dmLoader({ - i3dmResource: resource, - arrayBuffer: arrayBuffer, - }); - i3dmLoaders.push(loader); - loader.load(); - - return waitForLoaderProcess(loader, scene); - }).toThrowError(RuntimeError); + async function expectLoadError(arrayBuffer) { + const resource = Resource.createIfNeeded( + "http://example.com/content.i3dm" + ); + const loader = new I3dmLoader({ + i3dmResource: resource, + arrayBuffer: arrayBuffer, + }); + i3dmLoaders.push(loader); + await expectAsync( + (async () => { + await loader.load(); + await waitForLoaderProcess(loader, scene); + })() + ).toBeRejectedWithError(RuntimeError); } function verifyInstances(loader, expectedSemantics, instancesLength) { @@ -394,18 +394,18 @@ describe( }); }); - it("throws with invalid format", function () { + it("throws with invalid format", async function () { const arrayBuffer = Cesium3DTilesTester.generateInstancedTileBuffer({ gltfFormat: 2, }); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); - it("throws with invalid version", function () { + it("throws with invalid version", async function () { const arrayBuffer = Cesium3DTilesTester.generateInstancedTileBuffer({ version: 2, }); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); }, "WebGL" diff --git a/packages/engine/Specs/Scene/Model/InstancingPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/InstancingPipelineStageSpec.js index 6c77534d7d22..bc739a877da7 100644 --- a/packages/engine/Specs/Scene/Model/InstancingPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/InstancingPipelineStageSpec.js @@ -133,25 +133,23 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } - function loadI3dm(i3dmPath) { - const result = Resource.fetchArrayBuffer(i3dmPath); - - return result.then(function (arrayBuffer) { - const i3dmLoader = new I3dmLoader( - getI3dmOptions(i3dmPath, { arrayBuffer: arrayBuffer }) - ); - gltfLoaders.push(i3dmLoader); - i3dmLoader.load(); - return waitForLoaderProcess(i3dmLoader, scene); - }); + async function loadI3dm(i3dmPath) { + const arrayBuffer = await Resource.fetchArrayBuffer(i3dmPath); + const i3dmLoader = new I3dmLoader( + getI3dmOptions(i3dmPath, { arrayBuffer: arrayBuffer }) + ); + gltfLoaders.push(i3dmLoader); + await i3dmLoader.load(); + await waitForLoaderProcess(i3dmLoader, scene); + return i3dmLoader; } function verifyTypedArraysUnloaded(instances) { diff --git a/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js index 76770a79b07c..c5b9cd0edd57 100644 --- a/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js @@ -67,12 +67,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } const boomBox = "./Data/Models/glTF-2.0/BoomBox/glTF/BoomBox.gltf"; diff --git a/packages/engine/Specs/Scene/Model/MetadataPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/MetadataPipelineStageSpec.js index 42cd1bbc4c71..2a8f0f2197d8 100644 --- a/packages/engine/Specs/Scene/Model/MetadataPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/MetadataPipelineStageSpec.js @@ -56,15 +56,15 @@ describe( ResourceCache.clearForSpecs(); }); - function loadGltf(gltfPath) { + async function loadGltf(gltfPath) { const gltfLoader = new GltfLoader({ gltfResource: new Resource({ url: gltfPath }), incrementallyLoadTextures: false, }); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockRenderResources(components) { diff --git a/packages/engine/Specs/Scene/Model/Model3DTileContentSpec.js b/packages/engine/Specs/Scene/Model/Model3DTileContentSpec.js index efd033a07c3a..857ba637ed7d 100644 --- a/packages/engine/Specs/Scene/Model/Model3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Model/Model3DTileContentSpec.js @@ -1275,12 +1275,22 @@ describe( setCamera(centerLongitude, centerLatitude, 100.0); }); - it("resolves readyPromise with glb", function () { - return Cesium3DTilesTester.resolvesReadyPromise(scene, glbContentUrl); + it("becomes ready with glb", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + glbContentUrl + ); + expect(tileset.root.contentReady).toBeTrue(); + expect(tileset.root.content).toBeDefined(); }); - it("resolves readyPromise with glTF", function () { - return Cesium3DTilesTester.resolvesReadyPromise(scene, gltfContentUrl); + it("becomes ready with glTF", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + gltfContentUrl + ); + expect(tileset.root.contentReady).toBeTrue(); + expect(tileset.root.content).toBeDefined(); }); it("renders glb content", function () { diff --git a/packages/engine/Specs/Scene/Model/ModelAnimationCollectionSpec.js b/packages/engine/Specs/Scene/Model/ModelAnimationCollectionSpec.js index 17e690351336..cc938fab9721 100644 --- a/packages/engine/Specs/Scene/Model/ModelAnimationCollectionSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelAnimationCollectionSpec.js @@ -520,7 +520,7 @@ describe( ).then(function () { expect(spyStart).toHaveBeenCalledWith(model, animation); - expect(spyUpdate.calls.count()).toEqual(2); + expect(spyUpdate.calls.count()).toBeGreaterThanOrEqual(2); expect(spyUpdate.calls.argsFor(0)[0]).toBe(model); expect(spyUpdate.calls.argsFor(0)[1]).toBe(animation); diff --git a/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js b/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js index d4c912fff10b..eddd956faa54 100644 --- a/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js @@ -6,6 +6,7 @@ import { Matrix4, Math as CesiumMath, ModelDrawCommand, + ModelRuntimePrimitive, ResourceCache, Quaternion, } from "../../../index.js"; @@ -63,6 +64,13 @@ describe( material: { doubleSided: false, }, + attributes: [ + { + semantic: "POSITION", + max: new Cartesian3(0.5, 2.0, 0.0), + min: new Cartesian3(-0.5, 0.0, 0.0), + }, + ], }; function mockRenderResources(model) { @@ -90,30 +98,36 @@ describe( const rootNode = getParentRootNode(model); const rootDrawCommand = clone(drawCommand); - rootDrawCommand.modelMatrix = new Matrix4(); + rootDrawCommand.modelMatrix = Matrix4.clone(Matrix4.IDENTITY); rootDrawCommand.boundingVolume = new BoundingSphere(); - rootNode.runtimePrimitives.push({ - updateStages: [], - drawCommand: new ModelDrawCommand({ - command: rootDrawCommand, - primitiveRenderResources: renderResources, - }), + let runtimePrimitive = new ModelRuntimePrimitive({ + node: rootNode, + model: model, primitive: mockPrimitive, }); + runtimePrimitive.drawCommand = new ModelDrawCommand({ + command: rootDrawCommand, + primitiveRenderResources: renderResources, + }); + spyOn(runtimePrimitive, "configurePipeline"); + rootNode.runtimePrimitives.push(runtimePrimitive); rootNode._transformDirty = true; const leafNode = getChildLeafNode(model); const leafDrawCommand = clone(drawCommand); - leafDrawCommand.modelMatrix = new Matrix4(); + leafDrawCommand.modelMatrix = Matrix4.clone(Matrix4.IDENTITY); leafDrawCommand.boundingVolume = new BoundingSphere(); - leafNode.runtimePrimitives.push({ - updateStages: [], - drawCommand: new ModelDrawCommand({ - command: leafDrawCommand, - primitiveRenderResources: renderResources, - }), + runtimePrimitive = new ModelRuntimePrimitive({ + node: leafNode, + model: model, primitive: mockPrimitive, }); + runtimePrimitive.drawCommand = new ModelDrawCommand({ + command: leafDrawCommand, + primitiveRenderResources: renderResources, + }); + spyOn(runtimePrimitive, "configurePipeline"); + leafNode.runtimePrimitives.push(runtimePrimitive); leafNode._transformDirty = true; } @@ -209,9 +223,9 @@ describe( const staticLeafNode = getStaticLeafNode(model); const transformedLeafNode = getChildLeafNode(model); - const rootDrawCommand = getDrawCommand(rootNode); - const staticDrawCommand = getDrawCommand(staticLeafNode); - const transformedDrawCommand = getDrawCommand(transformedLeafNode); + let rootDrawCommand = getDrawCommand(rootNode); + let staticDrawCommand = getDrawCommand(staticLeafNode); + let transformedDrawCommand = getDrawCommand(transformedLeafNode); const childTransformation = Matrix4.fromTranslation( new Cartesian3(0, 5, 0) @@ -246,6 +260,9 @@ describe( ); scene.renderForSpecs(); + rootDrawCommand = getDrawCommand(rootNode); + staticDrawCommand = getDrawCommand(staticLeafNode); + transformedDrawCommand = getDrawCommand(transformedLeafNode); expect(rootDrawCommand.modelMatrix).toEqual(expectedRootModelMatrix); expect(staticDrawCommand.modelMatrix).toEqual( @@ -271,9 +288,9 @@ describe( const staticLeafNode = getStaticLeafNode(model); const transformedLeafNode = getChildLeafNode(model); - const rootDrawCommand = getDrawCommand(rootNode); - const staticDrawCommand = getDrawCommand(staticLeafNode); - const transformedDrawCommand = getDrawCommand(transformedLeafNode); + let rootDrawCommand = getDrawCommand(rootNode); + let staticDrawCommand = getDrawCommand(staticLeafNode); + let transformedDrawCommand = getDrawCommand(transformedLeafNode); const expectedRootModelMatrix = Matrix4.multiplyTransformation( modelMatrix, @@ -294,6 +311,10 @@ describe( model.modelMatrix = modelMatrix; scene.renderForSpecs(); + rootDrawCommand = getDrawCommand(rootNode); + staticDrawCommand = getDrawCommand(staticLeafNode); + transformedDrawCommand = getDrawCommand(transformedLeafNode); + expect(rootDrawCommand.modelMatrix).toEqual(expectedRootModelMatrix); expect(staticDrawCommand.modelMatrix).toEqual( expectedStaticLeafModelMatrix @@ -325,9 +346,9 @@ describe( const staticLeafNode = getStaticLeafNode(model); const transformedLeafNode = getChildLeafNode(model); - const rootDrawCommand = getDrawCommand(rootNode); - const staticDrawCommand = getDrawCommand(staticLeafNode); - const transformedDrawCommand = getDrawCommand(transformedLeafNode); + let rootDrawCommand = getDrawCommand(rootNode); + let staticDrawCommand = getDrawCommand(staticLeafNode); + let transformedDrawCommand = getDrawCommand(transformedLeafNode); const expectedRootModelMatrix = Matrix4.multiplyTransformation( scaledModelMatrix, @@ -348,6 +369,9 @@ describe( model.modelMatrix = modelMatrix; model.scale = modelScale; scene.renderForSpecs(); + rootDrawCommand = getDrawCommand(rootNode); + staticDrawCommand = getDrawCommand(staticLeafNode); + transformedDrawCommand = getDrawCommand(transformedLeafNode); expect(rootDrawCommand.modelMatrix).toEqual(expectedRootModelMatrix); expect(staticDrawCommand.modelMatrix).toEqual( diff --git a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js index e3e8f2873cd9..b5196ff355d3 100644 --- a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js @@ -170,8 +170,6 @@ describe( expect(ModelSceneGraph.prototype.pushDrawCommands).toHaveBeenCalled(); expect(frameState.commandList.length).toEqual(primitivesCount); - expect(model._drawCommandsBuilt).toEqual(true); - // Reset the draw command list to see if they're re-built. model._drawCommandsBuilt = false; frameState.commandList.length = 0; diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index 2ccd2cb068de..ee3c3260f582 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -345,16 +345,16 @@ describe( model.errorEvent.addEventListener((e) => { expect(e).toBeInstanceOf(RuntimeError); + expect(e.message).toContain("Failed to load texture"); expect(e.message).toContain( - `Failed to load model: ${boxTexturedGltfUrl}` + "Failed to load image: non-existent-path.png" ); - expect(e.message).toContain("Failed to load texture"); finished = true; }); return pollToPromise(function () { scene.renderForSpecs(); - return finished; + return model.ready && finished; }); }); diff --git a/packages/engine/Specs/Scene/Model/MorphTargetsPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/MorphTargetsPipelineStageSpec.js index 3bf6dea562e0..82b547bd50d6 100644 --- a/packages/engine/Specs/Scene/Model/MorphTargetsPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/MorphTargetsPipelineStageSpec.js @@ -51,12 +51,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function verifyMorphTargetAttribute( diff --git a/packages/engine/Specs/Scene/Model/NodeStatisticsPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/NodeStatisticsPipelineStageSpec.js index f301ba119160..d900e93dbbd8 100644 --- a/packages/engine/Specs/Scene/Model/NodeStatisticsPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/NodeStatisticsPipelineStageSpec.js @@ -54,12 +54,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockRenderResources(components) { diff --git a/packages/engine/Specs/Scene/Model/PickingPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/PickingPipelineStageSpec.js index 180ffe197d30..5e44cd7e1c69 100644 --- a/packages/engine/Specs/Scene/Model/PickingPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/PickingPipelineStageSpec.js @@ -58,12 +58,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockRenderResources() { diff --git a/packages/engine/Specs/Scene/Model/PntsLoaderSpec.js b/packages/engine/Specs/Scene/Model/PntsLoaderSpec.js index 9b4c030a1d20..5fec3e9e9999 100644 --- a/packages/engine/Specs/Scene/Model/PntsLoaderSpec.js +++ b/packages/engine/Specs/Scene/Model/PntsLoaderSpec.js @@ -79,15 +79,16 @@ describe( ResourceCache.clearForSpecs(); }); - function loadPntsArrayBuffer(arrayBuffer, options) { + async function loadPntsArrayBuffer(arrayBuffer, options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const loader = new PntsLoader({ arrayBuffer: arrayBuffer, loadAttributesFor2D: options.loadAttributesFor2D, }); pntsLoaders.push(loader); - loader.load(); - return waitForLoaderProcess(loader, scene); + await loader.load(); + await waitForLoaderProcess(loader, scene); + return loader; } function loadPnts(pntsPath, options) { @@ -98,10 +99,10 @@ describe( }); } - function expectLoadError(arrayBuffer) { - expect(function () { - return loadPntsArrayBuffer(arrayBuffer); - }).toThrowError(RuntimeError); + async function expectLoadError(arrayBuffer) { + await expectAsync(loadPntsArrayBuffer(arrayBuffer)).toBeRejectedWithError( + RuntimeError + ); } function expectEmptyMetadata(structuralMetadata) { @@ -765,21 +766,21 @@ describe( ); }); - it("throws with invalid version", function () { + it("throws with invalid version", async function () { const arrayBuffer = Cesium3DTilesTester.generatePointCloudTileBuffer({ version: 2, }); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); - it("throws if featureTableJsonByteLength is 0", function () { + it("throws if featureTableJsonByteLength is 0", async function () { const arrayBuffer = Cesium3DTilesTester.generatePointCloudTileBuffer({ featureTableJsonByteLength: 0, }); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); - it("throws if the feature table does not contain POINTS_LENGTH", function () { + it("throws if the feature table does not contain POINTS_LENGTH", async function () { const arrayBuffer = Cesium3DTilesTester.generatePointCloudTileBuffer({ featureTableJson: { POSITION: { @@ -787,19 +788,19 @@ describe( }, }, }); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); - it("throws if the feature table does not contain POSITION or POSITION_QUANTIZED", function () { + it("throws if the feature table does not contain POSITION or POSITION_QUANTIZED", async function () { const arrayBuffer = Cesium3DTilesTester.generatePointCloudTileBuffer({ featureTableJson: { POINTS_LENGTH: 1, }, }); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); - it("throws if the positions are quantized and the feature table does not contain QUANTIZED_VOLUME_SCALE", function () { + it("throws if the positions are quantized and the feature table does not contain QUANTIZED_VOLUME_SCALE", async function () { const arrayBuffer = Cesium3DTilesTester.generatePointCloudTileBuffer({ featureTableJson: { POINTS_LENGTH: 1, @@ -809,10 +810,10 @@ describe( QUANTIZED_VOLUME_OFFSET: [0.0, 0.0, 0.0], }, }); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); - it("throws if the positions are quantized and the feature table does not contain QUANTIZED_VOLUME_OFFSET", function () { + it("throws if the positions are quantized and the feature table does not contain QUANTIZED_VOLUME_OFFSET", async function () { const arrayBuffer = Cesium3DTilesTester.generatePointCloudTileBuffer({ featureTableJson: { POINTS_LENGTH: 1, @@ -822,10 +823,10 @@ describe( QUANTIZED_VOLUME_SCALE: [1.0, 1.0, 1.0], }, }); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); - it("throws if the BATCH_ID semantic is defined but BATCH_LENGTH is not", function () { + it("throws if the BATCH_ID semantic is defined but BATCH_LENGTH is not", async function () { const arrayBuffer = Cesium3DTilesTester.generatePointCloudTileBuffer({ featureTableJson: { POINTS_LENGTH: 2, @@ -833,29 +834,24 @@ describe( BATCH_ID: [0, 1], }, }); - expectLoadError(arrayBuffer); + await expectLoadError(arrayBuffer); }); - it("error decoding a draco point cloud causes loading to fail", function () { + it("error decoding a draco point cloud causes loading to fail", async function () { const readyPromise = pollToPromise(function () { return DracoLoader._taskProcessorReady; }); DracoLoader._getDecoderTaskProcessor(); - return readyPromise - .then(function () { - const decoder = DracoLoader._getDecoderTaskProcessor(); - spyOn(decoder, "scheduleTask").and.callFake(function () { - return Promise.reject({ message: "my error" }); - }); - - return loadPnts(pointCloudDracoUrl); - }) - .then(function () { - fail("should not resolve"); - }) - .catch(function (error) { - expect(error.message).toBe("Failed to load Draco pnts\nmy error"); - }); + await readyPromise; + const decoder = DracoLoader._getDecoderTaskProcessor(); + spyOn(decoder, "scheduleTask").and.callFake(function () { + return Promise.reject({ message: "my error" }); + }); + + await expectAsync(loadPnts(pointCloudDracoUrl)).toBeRejectedWithError( + RuntimeError, + "Failed to load Draco pnts\nmy error" + ); }); it("destroys pnts loader", function () { diff --git a/packages/engine/Specs/Scene/Model/PrimitiveOutlinePipelineStageSpec.js b/packages/engine/Specs/Scene/Model/PrimitiveOutlinePipelineStageSpec.js index 4a124406ce7d..fa37b883cca6 100644 --- a/packages/engine/Specs/Scene/Model/PrimitiveOutlinePipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/PrimitiveOutlinePipelineStageSpec.js @@ -59,12 +59,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockRenderResources() { diff --git a/packages/engine/Specs/Scene/Model/PrimitiveStatisticsPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/PrimitiveStatisticsPipelineStageSpec.js index d7e9cc580219..54c2840a8f33 100644 --- a/packages/engine/Specs/Scene/Model/PrimitiveStatisticsPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/PrimitiveStatisticsPipelineStageSpec.js @@ -78,12 +78,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockModel(components) { diff --git a/packages/engine/Specs/Scene/Model/SceneMode2DPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/SceneMode2DPipelineStageSpec.js index 11b7a49ea70a..e5998f7f031e 100644 --- a/packages/engine/Specs/Scene/Model/SceneMode2DPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/SceneMode2DPipelineStageSpec.js @@ -64,12 +64,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockRenderResources() { diff --git a/packages/engine/Specs/Scene/Model/SelectedFeatureIdPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/SelectedFeatureIdPipelineStageSpec.js index 67e9d4af542c..afa2f5da13d7 100644 --- a/packages/engine/Specs/Scene/Model/SelectedFeatureIdPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/SelectedFeatureIdPipelineStageSpec.js @@ -83,12 +83,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockRenderResources(node) { diff --git a/packages/engine/Specs/Scene/Model/SkinningPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/SkinningPipelineStageSpec.js index ade501bca766..4449c281bfdb 100644 --- a/packages/engine/Specs/Scene/Model/SkinningPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/SkinningPipelineStageSpec.js @@ -50,12 +50,12 @@ describe( }); } - function loadGltf(gltfPath, options) { + async function loadGltf(gltfPath, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } const simpleSkinUrl = @@ -139,7 +139,6 @@ describe( return loadGltf(cesiumManUrl).then(function (gltfLoader) { const components = gltfLoader.components; const primitive = components.nodes[1].primitives[0]; - console.log(components); SkinningPipelineStage.process(renderResources, primitive); diff --git a/packages/engine/Specs/Scene/Model/WireframePipelineStageSpec.js b/packages/engine/Specs/Scene/Model/WireframePipelineStageSpec.js index f7e93c7892cf..5cf9183b999c 100644 --- a/packages/engine/Specs/Scene/Model/WireframePipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/WireframePipelineStageSpec.js @@ -64,12 +64,12 @@ describe( }); } - function loadGltf(gltfPath, scene, options) { + async function loadGltf(gltfPath, scene, options) { const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); - gltfLoader.load(); - - return waitForLoaderProcess(gltfLoader, scene); + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + return gltfLoader; } function mockRenderResources(primitive) { diff --git a/packages/engine/Specs/Scene/Multiple3DTileContentSpec.js b/packages/engine/Specs/Scene/Multiple3DTileContentSpec.js index 12a79c9bc3d0..7e84bd96d4e2 100644 --- a/packages/engine/Specs/Scene/Multiple3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Multiple3DTileContentSpec.js @@ -3,6 +3,7 @@ import { Cesium3DContentGroup, Cesium3DTileset, Color, + Event, HeadingPitchRange, Multiple3DTileContent, MetadataClass, @@ -175,10 +176,11 @@ describe( ]); }); - it("contentsFetchedPromise is undefined until requestInnerContents is successful", function () { + it("requestInnerContents returns promise that resolves to content if successful", async function () { const mockTileset = { statistics: { numberOfPendingRequests: 0, + numberOfAttemptedRequests: 0, }, }; const tile = {}; @@ -189,19 +191,24 @@ describe( contentsJson ); - expect(content.contentsFetchedPromise).not.toBeDefined(); - spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { return Promise.resolve(makeGltfBuffer()); }); - content.requestInnerContents(); - expect(content.contentsFetchedPromise).toBeDefined(); + + const promise = content.requestInnerContents(); + expect(mockTileset.statistics.numberOfPendingRequests).toBe(3); + expect(mockTileset.statistics.numberOfAttemptedRequests).toBe(0); + + await expectAsync(promise).toBeResolvedTo(jasmine.any(Array)); + expect(mockTileset.statistics.numberOfPendingRequests).toBe(0); + expect(mockTileset.statistics.numberOfAttemptedRequests).toBe(0); }); - it("contentsFetchedPromise is undefined if no requests are scheduled", function () { + it("requestInnerContents returns undefined and updates statistics if all requests cannot be scheduled", function () { const mockTileset = { statistics: { numberOfPendingRequests: 0, + numberOfAttemptedRequests: 0, }, }; const tile = {}; @@ -212,19 +219,19 @@ describe( contentsJson ); - expect(content.contentsFetchedPromise).not.toBeDefined(); - RequestScheduler.maximumRequestsPerServer = 2; - content.requestInnerContents(); - - expect(content.contentsFetchedPromise).not.toBeDefined(); + expect(content.requestInnerContents()).toBeUndefined(); + expect(mockTileset.statistics.numberOfPendingRequests).toBe(0); + expect(mockTileset.statistics.numberOfAttemptedRequests).toBe(3); }); - it("requestInnerContents returns 0 if successful", function () { + it("requestInnerContents handles inner content failures", async function () { const mockTileset = { statistics: { numberOfPendingRequests: 0, + numberOfAttemptedRequests: 0, }, + tileFailed: new Event(), }; const tile = {}; const content = new Multiple3DTileContent( @@ -234,20 +241,32 @@ describe( contentsJson ); - const fetchArray = spyOn( - Resource.prototype, - "fetchArrayBuffer" - ).and.callFake(function () { - return Promise.resolve(makeGltfBuffer()); + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { + return Promise.reject(new Error("my error")); }); - expect(content.requestInnerContents()).toBe(0); - expect(fetchArray.calls.count()).toBe(3); + + const failureSpy = jasmine.createSpy(); + mockTileset.tileFailed.addEventListener(failureSpy); + + const promise = content.requestInnerContents(); + expect(mockTileset.statistics.numberOfPendingRequests).toBe(3); + expect(mockTileset.statistics.numberOfAttemptedRequests).toBe(0); + + await expectAsync(promise).toBeResolved(); + expect(mockTileset.statistics.numberOfPendingRequests).toBe(0); + expect(mockTileset.statistics.numberOfAttemptedRequests).toBe(0); + expect(failureSpy).toHaveBeenCalledWith( + jasmine.objectContaining({ + message: "my error", + }) + ); }); - it("requestInnerContents schedules no requests if there are not enough open slots", function () { + it("requestInnerContents handles cancelled requests", async function () { const mockTileset = { statistics: { numberOfPendingRequests: 0, + numberOfAttemptedRequests: 0, }, }; const tile = {}; @@ -258,17 +277,28 @@ describe( contentsJson ); - const fetchArray = spyOn(Resource.prototype, "fetchArrayBuffer"); - RequestScheduler.maximumRequestsPerServer = 2; - expect(content.requestInnerContents()).toBe(3); - expect(fetchArray).not.toHaveBeenCalled(); + spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { + return Promise.resolve(makeGltfBuffer()); + }); + + const promise = content.requestInnerContents(); + expect(mockTileset.statistics.numberOfPendingRequests).toBe(3); + expect(mockTileset.statistics.numberOfAttemptedRequests).toBe(0); + + content.cancelRequests(); + + await expectAsync(promise).toBeResolved(); + expect(mockTileset.statistics.numberOfPendingRequests).toBe(0); + expect(mockTileset.statistics.numberOfAttemptedRequests).toBe(3); }); - it("resolves readyPromise", function () { - return Cesium3DTilesTester.resolvesReadyPromise( + it("becomes ready", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( scene, multipleContentsUrl ); + expect(tileset.root.contentReady).toBeTrue(); + expect(tileset.root.content).toBeDefined(); }); it("renders multiple contents", function () { diff --git a/packages/engine/Specs/Scene/ResourceCacheStatisticsSpec.js b/packages/engine/Specs/Scene/ResourceCacheStatisticsSpec.js index bc2c0db09154..b73e3f9f18c1 100644 --- a/packages/engine/Specs/Scene/ResourceCacheStatisticsSpec.js +++ b/packages/engine/Specs/Scene/ResourceCacheStatisticsSpec.js @@ -9,280 +9,164 @@ describe("Scene/ResourceCacheStatistics", function () { expect(statistics._textureSizes).toEqual({}); }); - function mockLoader(cacheKey, shouldResolve, data) { - const loader = combine(data, { + function mockLoader(cacheKey, data) { + return combine(data, { cacheKey: cacheKey, }); - - if (shouldResolve) { - loader.promise = Promise.resolve(loader); - } else { - loader.promise = Promise.reject(); - } - - return loader; - } - - function mockCanceledLoader(cacheKey, data) { - const loader = combine(data, { - cacheKey: cacheKey, - }); - - loader.promise = new Promise(function (resolve) { - // Deferred so the unit test can decide when the cancellation happens - // via loader.cancel(); - loader.cancel = resolve; - }); - - return loader; } it("clears", function () { const statistics = new ResourceCacheStatistics(); - const geometryLoader = mockLoader("vertices", true, { + const geometryLoader = mockLoader("vertices", { buffer: { sizeInBytes: 100, }, }); - const textureLoader = mockLoader("texture", true, { + const textureLoader = mockLoader("texture", { texture: { sizeInBytes: 300, }, }); - const geometryPromise = statistics.addGeometryLoader(geometryLoader); - const texturePromise = statistics.addTextureLoader(textureLoader); + statistics.addGeometryLoader(geometryLoader); + statistics.addTextureLoader(textureLoader); - return Promise.all([geometryPromise, texturePromise]).then(function () { - expect(statistics.geometryByteLength).not.toBe(0); - expect(statistics.texturesByteLength).not.toBe(0); - expect(statistics._geometrySizes).not.toEqual({}); - expect(statistics._textureSizes).not.toEqual({}); + expect(statistics.geometryByteLength).not.toBe(0); + expect(statistics.texturesByteLength).not.toBe(0); + expect(statistics._geometrySizes).not.toEqual({}); + expect(statistics._textureSizes).not.toEqual({}); - statistics.clear(); + statistics.clear(); - expect(statistics.geometryByteLength).toBe(0); - expect(statistics.texturesByteLength).toBe(0); - expect(statistics._geometrySizes).toEqual({}); - expect(statistics._textureSizes).toEqual({}); - }); + expect(statistics.geometryByteLength).toBe(0); + expect(statistics.texturesByteLength).toBe(0); + expect(statistics._geometrySizes).toEqual({}); + expect(statistics._textureSizes).toEqual({}); }); it("addGeometryLoader throws for undefined loader", function () { const statistics = new ResourceCacheStatistics(); expect(function () { - return statistics.addGeometryLoader(undefined); + statistics.addGeometryLoader(undefined); }).toThrowDeveloperError(); }); it("addGeometryLoader counts geometry memory", function () { const statistics = new ResourceCacheStatistics(); - const geometryLoader = mockLoader("vertices", true, { + const geometryLoader = mockLoader("vertices", { buffer: { sizeInBytes: 100, }, }); - return statistics.addGeometryLoader(geometryLoader).then(function () { - expect(statistics.geometryByteLength).toBe(100); - expect(statistics.texturesByteLength).toBe(0); - expect(statistics._geometrySizes).toEqual({ vertices: 100 }); - expect(statistics._textureSizes).toEqual({}); - }); + statistics.addGeometryLoader(geometryLoader); + expect(statistics.geometryByteLength).toBe(100); + expect(statistics.texturesByteLength).toBe(0); + expect(statistics._geometrySizes).toEqual({ vertices: 100 }); + expect(statistics._textureSizes).toEqual({}); }); it("addGeometryLoader counts buffers with typed arrays", function () { const statistics = new ResourceCacheStatistics(); - const geometryLoader = mockLoader("vertices", true, { + const geometryLoader = mockLoader("vertices", { buffer: { sizeInBytes: 100, }, typedArray: new Uint8Array(100), }); - return statistics.addGeometryLoader(geometryLoader).then(function () { - expect(statistics.geometryByteLength).toBe(200); - expect(statistics.texturesByteLength).toBe(0); - expect(statistics._geometrySizes).toEqual({ vertices: 200 }); - expect(statistics._textureSizes).toEqual({}); - }); + statistics.addGeometryLoader(geometryLoader); + expect(statistics.geometryByteLength).toBe(200); + expect(statistics.texturesByteLength).toBe(0); + expect(statistics._geometrySizes).toEqual({ vertices: 200 }); + expect(statistics._textureSizes).toEqual({}); }); it("addGeometryLoader does not double count memory", function () { const statistics = new ResourceCacheStatistics(); - const geometryLoader = mockLoader("vertices", true, { + const geometryLoader = mockLoader("vertices", { buffer: { sizeInBytes: 100, }, typedArray: new Uint8Array(100), }); - const promise = statistics.addGeometryLoader(geometryLoader); - const promise2 = statistics.addGeometryLoader(geometryLoader); - - return Promise.all([promise, promise2]).then(function () { - expect(statistics.geometryByteLength).toBe(200); - expect(statistics.texturesByteLength).toBe(0); - expect(statistics._geometrySizes).toEqual({ vertices: 200 }); - expect(statistics._textureSizes).toEqual({}); - }); - }); - - it("addGeometryLoader handles failed buffer load", function () { - const statistics = new ResourceCacheStatistics(); - - const geometryLoader = mockLoader("vertices", false, { - buffer: { - sizeInBytes: 100, - }, - }); - - return statistics.addGeometryLoader(geometryLoader).then(function () { - expect(statistics.geometryByteLength).toBe(0); - expect(statistics.texturesByteLength).toBe(0); - expect(statistics._geometrySizes).toEqual({}); - expect(statistics._textureSizes).toEqual({}); - }); - }); - - it("addGeometryLoader handles canceled buffer load", function () { - const statistics = new ResourceCacheStatistics(); - - const geometryLoader = mockCanceledLoader("vertices", { - buffer: { - sizeInBytes: 100, - }, - }); - - const promise = statistics.addGeometryLoader(geometryLoader); - - expect(statistics.geometryByteLength).toBe(0); - expect(statistics._geometrySizes).toEqual({ vertices: 0 }); + statistics.addGeometryLoader(geometryLoader); + statistics.addGeometryLoader(geometryLoader); - // simulate removing the loader before the promise resolves - statistics.removeLoader(geometryLoader); - geometryLoader.cancel(); - - return promise.then(function () { - expect(statistics.geometryByteLength).toBe(0); - expect(statistics.texturesByteLength).toBe(0); - expect(statistics._geometrySizes).toEqual({}); - expect(statistics._textureSizes).toEqual({}); - }); + expect(statistics.geometryByteLength).toBe(200); + expect(statistics.texturesByteLength).toBe(0); + expect(statistics._geometrySizes).toEqual({ vertices: 200 }); + expect(statistics._textureSizes).toEqual({}); }); it("addTextureLoader throws for undefined loader", function () { const statistics = new ResourceCacheStatistics(); expect(function () { - return statistics.addTextureLoader(undefined); + statistics.addTextureLoader(undefined); }).toThrowDeveloperError(); }); it("addTextureLoader counts texture memory", function () { const statistics = new ResourceCacheStatistics(); - const textureLoader = mockLoader("texture", true, { - texture: { - sizeInBytes: 100, - }, - }); - - return statistics.addTextureLoader(textureLoader).then(function () { - expect(statistics.geometryByteLength).toBe(0); - expect(statistics.texturesByteLength).toBe(100); - expect(statistics._geometrySizes).toEqual({}); - expect(statistics._textureSizes).toEqual({ texture: 100 }); - }); - }); - - it("addTextureLoader handles failed texture load", function () { - const statistics = new ResourceCacheStatistics(); - - const textureLoader = mockLoader("texture", false, { - texture: { - sizeInBytes: 100, - }, - }); - - return statistics.addTextureLoader(textureLoader).then(function () { - expect(statistics.geometryByteLength).toBe(0); - expect(statistics.texturesByteLength).toBe(0); - expect(statistics._geometrySizes).toEqual({}); - expect(statistics._textureSizes).toEqual({}); - }); - }); - - it("addTextureLoader handles canceled texture load", function () { - const statistics = new ResourceCacheStatistics(); - - const textureLoader = mockCanceledLoader("texture", { + const textureLoader = mockLoader("texture", { texture: { sizeInBytes: 100, }, }); - const promise = statistics.addTextureLoader(textureLoader); - - expect(statistics.texturesByteLength).toBe(0); - expect(statistics._textureSizes).toEqual({ texture: 0 }); - - // simulate removing the loader before the promise resolves - statistics.removeLoader(textureLoader); - textureLoader.cancel(); - - return promise.then(function () { - expect(statistics.geometryByteLength).toBe(0); - expect(statistics.texturesByteLength).toBe(0); - expect(statistics._geometrySizes).toEqual({}); - expect(statistics._textureSizes).toEqual({}); - }); + statistics.addTextureLoader(textureLoader); + expect(statistics.geometryByteLength).toBe(0); + expect(statistics.texturesByteLength).toBe(100); + expect(statistics._geometrySizes).toEqual({}); + expect(statistics._textureSizes).toEqual({ texture: 100 }); }); it("addTextureLoader does not double count memory", function () { const statistics = new ResourceCacheStatistics(); - const textureLoader = mockLoader("texture", true, { + const textureLoader = mockLoader("texture", { texture: { sizeInBytes: 100, }, }); - const promise = statistics.addTextureLoader(textureLoader); - const promise2 = statistics.addTextureLoader(textureLoader); - return Promise.all([promise, promise2]).then(function () { - expect(statistics.geometryByteLength).toBe(0); - expect(statistics.texturesByteLength).toBe(100); - expect(statistics._geometrySizes).toEqual({}); - expect(statistics._textureSizes).toEqual({ texture: 100 }); - }); + statistics.addTextureLoader(textureLoader); + statistics.addTextureLoader(textureLoader); + + expect(statistics.geometryByteLength).toBe(0); + expect(statistics.texturesByteLength).toBe(100); + expect(statistics._geometrySizes).toEqual({}); + expect(statistics._textureSizes).toEqual({ texture: 100 }); }); it("removeLoader throws for undefined loader", function () { const statistics = new ResourceCacheStatistics(); expect(function () { - return statistics.removeLoader(undefined); + statistics.removeLoader(undefined); }).toThrowDeveloperError(); }); it("removeLoader correctly updates memory for buffers", function () { - const geometryLoader = mockLoader("vertices", true, { + const geometryLoader = mockLoader("vertices", { buffer: { sizeInBytes: 100, }, }); - const geometryLoader2 = mockLoader("indices", true, { + const geometryLoader2 = mockLoader("indices", { buffer: { sizeInBytes: 200, }, }); - const textureLoader = mockLoader("texture", true, { + const textureLoader = mockLoader("texture", { texture: { sizeInBytes: 300, }, @@ -290,45 +174,39 @@ describe("Scene/ResourceCacheStatistics", function () { const statistics = new ResourceCacheStatistics(); - const geometryPromise = statistics.addGeometryLoader(geometryLoader); - const geometryPromise2 = statistics.addGeometryLoader(geometryLoader2); - const texturePromise = statistics.addTextureLoader(textureLoader); - - return Promise.all([ - geometryPromise, - geometryPromise2, - texturePromise, - ]).then(function () { - expect(statistics.geometryByteLength).toBe(300); - expect(statistics.texturesByteLength).toBe(300); - expect(statistics._geometrySizes).toEqual({ - vertices: 100, - indices: 200, - }); - - statistics.removeLoader(geometryLoader2); - - expect(statistics.geometryByteLength).toBe(100); - expect(statistics.texturesByteLength).toBe(300); - expect(statistics._geometrySizes).toEqual({ vertices: 100 }); - expect(statistics._textureSizes).toEqual({ texture: 300 }); + statistics.addGeometryLoader(geometryLoader); + statistics.addGeometryLoader(geometryLoader2); + statistics.addTextureLoader(textureLoader); + + expect(statistics.geometryByteLength).toBe(300); + expect(statistics.texturesByteLength).toBe(300); + expect(statistics._geometrySizes).toEqual({ + vertices: 100, + indices: 200, }); + + statistics.removeLoader(geometryLoader2); + + expect(statistics.geometryByteLength).toBe(100); + expect(statistics.texturesByteLength).toBe(300); + expect(statistics._geometrySizes).toEqual({ vertices: 100 }); + expect(statistics._textureSizes).toEqual({ texture: 300 }); }); it("removeLoader correctly updates memory for textures", function () { - const geometryLoader = mockLoader("vertices", true, { + const geometryLoader = mockLoader("vertices", { buffer: { sizeInBytes: 100, }, }); - const textureLoader = mockLoader("texture", true, { + const textureLoader = mockLoader("texture", { texture: { sizeInBytes: 300, }, }); - const textureLoader2 = mockLoader("texture2", true, { + const textureLoader2 = mockLoader("texture2", { texture: { sizeInBytes: 500, }, @@ -336,40 +214,36 @@ describe("Scene/ResourceCacheStatistics", function () { const statistics = new ResourceCacheStatistics(); - const geometryPromise = statistics.addGeometryLoader(geometryLoader); - const texturePromise = statistics.addTextureLoader(textureLoader); - const texturePromise2 = statistics.addTextureLoader(textureLoader2); - - return Promise.all([geometryPromise, texturePromise, texturePromise2]).then( - function () { - expect(statistics.geometryByteLength).toBe(100); - expect(statistics.texturesByteLength).toBe(800); - expect(statistics._geometrySizes).toEqual({ vertices: 100 }); - expect(statistics._textureSizes).toEqual({ - texture: 300, - texture2: 500, - }); - - statistics.removeLoader(textureLoader2); - - expect(statistics.geometryByteLength).toBe(100); - expect(statistics.texturesByteLength).toBe(300); - expect(statistics._geometrySizes).toEqual({ vertices: 100 }); - expect(statistics._textureSizes).toEqual({ texture: 300 }); - } - ); + statistics.addGeometryLoader(geometryLoader); + statistics.addTextureLoader(textureLoader); + statistics.addTextureLoader(textureLoader2); + + expect(statistics.geometryByteLength).toBe(100); + expect(statistics.texturesByteLength).toBe(800); + expect(statistics._geometrySizes).toEqual({ vertices: 100 }); + expect(statistics._textureSizes).toEqual({ + texture: 300, + texture2: 500, + }); + + statistics.removeLoader(textureLoader2); + + expect(statistics.geometryByteLength).toBe(100); + expect(statistics.texturesByteLength).toBe(300); + expect(statistics._geometrySizes).toEqual({ vertices: 100 }); + expect(statistics._textureSizes).toEqual({ texture: 300 }); }); it("removeLoader gracefully handles loader without tracked resources", function () { const statistics = new ResourceCacheStatistics(); - const textureLoader = mockLoader("texture", true, { + const textureLoader = mockLoader("texture", { texture: { sizeInBytes: 300, }, }); expect(function () { - return statistics.removeLoader(textureLoader); + statistics.removeLoader(textureLoader); }).not.toThrowDeveloperError(); expect(statistics.geometryByteLength).toBe(0); diff --git a/packages/engine/Specs/Scene/ResourceLoaderSpec.js b/packages/engine/Specs/Scene/ResourceLoaderSpec.js index 39fbd6cd069e..59a58452255e 100644 --- a/packages/engine/Specs/Scene/ResourceLoaderSpec.js +++ b/packages/engine/Specs/Scene/ResourceLoaderSpec.js @@ -3,9 +3,6 @@ import { ResourceLoader } from "../../index.js"; describe("Scene/ResourceLoader", function () { it("throws when using ResourceLoader directly", function () { const resourceLoader = new ResourceLoader(); - expect(function () { - return resourceLoader.promise; - }).toThrowDeveloperError(); expect(function () { return resourceLoader.cacheKey; }).toThrowDeveloperError(); diff --git a/packages/engine/Specs/Scene/ShadowMapSpec.js b/packages/engine/Specs/Scene/ShadowMapSpec.js index 8e472ae164d8..8cfd0db1f740 100644 --- a/packages/engine/Specs/Scene/ShadowMapSpec.js +++ b/packages/engine/Specs/Scene/ShadowMapSpec.js @@ -282,7 +282,7 @@ describe( } async function loadModel(options) { - const model = scene.primitives.add(await Model.fromGltf(options)); + const model = scene.primitives.add(await Model.fromGltfAsync(options)); await pollToPromise( function () { // Render scene to progressively load the model diff --git a/packages/engine/Specs/Scene/Tileset3DTileContentSpec.js b/packages/engine/Specs/Scene/Tileset3DTileContentSpec.js index 39d9cabc8431..f161f7dbe494 100644 --- a/packages/engine/Specs/Scene/Tileset3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Tileset3DTileContentSpec.js @@ -35,11 +35,13 @@ describe( scene.primitives.removeAll(); }); - it("resolves readyPromise", function () { - return Cesium3DTilesTester.resolvesReadyPromise( + it("becomes ready", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( scene, tilesetOfTilesetsUrl ); + expect(tileset.root.contentReady).toBeTrue(); + expect(tileset.root.content).toBeDefined(); }); it("destroys", function () { @@ -58,7 +60,6 @@ describe( expect(content.texturesByteLength).toBe(0); expect(content.batchTableByteLength).toBe(0); expect(content.innerContents).toBeUndefined(); - expect(content.readyPromise).toBeDefined(); expect(content.tileset).toBe(tileset); expect(content.tile).toBe(tile); expect(content.url).toBeDefined(); diff --git a/packages/engine/Specs/Scene/Vector3DTileContentSpec.js b/packages/engine/Specs/Scene/Vector3DTileContentSpec.js index 8091aaeed532..6bdf4724dc7f 100644 --- a/packages/engine/Specs/Scene/Vector3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Vector3DTileContentSpec.js @@ -19,6 +19,7 @@ import { Rectangle, RectangleGeometry, RenderState, + RuntimeError, StencilConstants, } from "../../index.js"; @@ -2165,29 +2166,44 @@ describe( }); }); - it("throws with invalid version", function () { + it("throws with invalid version", async function () { const arrayBuffer = Cesium3DTilesTester.generateVectorTileBuffer({ version: 2, }); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "vctr"); + await expectAsync( + Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "vctr") + ).toBeRejectedWithError( + RuntimeError, + "Only Vector tile version 1 is supported. Version 2 is not." + ); }); - it("throws with empty feature table", function () { + it("throws with empty feature table", async function () { const arrayBuffer = Cesium3DTilesTester.generateVectorTileBuffer({ defineFeatureTable: false, }); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "vctr"); + await expectAsync( + Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "vctr") + ).toBeRejectedWithError( + RuntimeError, + "Feature table must have a byte length greater than zero" + ); }); - it("throws without region", function () { + it("throws without region", async function () { const arrayBuffer = Cesium3DTilesTester.generateVectorTileBuffer({ defineRegion: false, polygonsLength: 1, }); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "vctr"); + await expectAsync( + Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "vctr") + ).toBeRejectedWithError( + RuntimeError, + "Feature table global property: REGION must be defined" + ); }); - it("throws without all batch ids", function () { + it("throws without all batch ids", async function () { const arrayBuffer = Cesium3DTilesTester.generateVectorTileBuffer({ polygonsLength: 1, pointsLength: 1, @@ -2195,7 +2211,12 @@ describe( polygonBatchIds: [1], pointBatchIds: [0], }); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "vctr"); + await expectAsync( + Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "vctr") + ).toBeRejectedWithError( + RuntimeError, + "If one group of batch ids is defined, then all batch ids must be defined" + ); }); it("destroys", function () { diff --git a/packages/engine/Specs/Scene/Vector3DTileGeometrySpec.js b/packages/engine/Specs/Scene/Vector3DTileGeometrySpec.js index 8a91e0fff2eb..f5c9d67609be 100644 --- a/packages/engine/Specs/Scene/Vector3DTileGeometrySpec.js +++ b/packages/engine/Specs/Scene/Vector3DTileGeometrySpec.js @@ -168,14 +168,10 @@ describe( }); function loadGeometries(geometries) { - let ready = false; - geometries.readyPromise.then(function () { - ready = true; - }); return pollToPromise(function () { geometries.update(scene.frameState); scene.frameState.commandList.length = 0; - return ready; + return geometries.ready; }); } diff --git a/packages/engine/Specs/Scene/Vector3DTilePointsSpec.js b/packages/engine/Specs/Scene/Vector3DTilePointsSpec.js index 9b62ed6d0120..9aef5e30df91 100644 --- a/packages/engine/Specs/Scene/Vector3DTilePointsSpec.js +++ b/packages/engine/Specs/Scene/Vector3DTilePointsSpec.js @@ -83,14 +83,10 @@ describe( } function loadPoints(points) { - let ready = false; - points.readyPromise.then(function () { - ready = true; - }); return pollToPromise(function () { points.update(scene.frameState); scene.frameState.commandList.length = 0; - return ready; + return points.ready; }); } diff --git a/packages/engine/Specs/Scene/Vector3DTilePolygonsSpec.js b/packages/engine/Specs/Scene/Vector3DTilePolygonsSpec.js index 9f2e022b9bf8..0388cf89f108 100644 --- a/packages/engine/Specs/Scene/Vector3DTilePolygonsSpec.js +++ b/packages/engine/Specs/Scene/Vector3DTilePolygonsSpec.js @@ -176,14 +176,10 @@ describe( }); function loadPolygons(polygons) { - let ready = false; - polygons.readyPromise.then(function () { - ready = true; - }); return pollToPromise(function () { polygons.update(scene.frameState); scene.frameState.commandList.length = 0; - return ready; + return polygons.ready; }); } diff --git a/packages/engine/Specs/Scene/Vector3DTilePolylinesSpec.js b/packages/engine/Specs/Scene/Vector3DTilePolylinesSpec.js index c42d108948e0..7a82e8db713d 100644 --- a/packages/engine/Specs/Scene/Vector3DTilePolylinesSpec.js +++ b/packages/engine/Specs/Scene/Vector3DTilePolylinesSpec.js @@ -56,14 +56,10 @@ describe( }); function loadPolylines(polylines) { - let ready = false; - polylines.readyPromise.then(function () { - ready = true; - }); return pollToPromise(function () { polylines.update(scene.frameState); scene.frameState.commandList.length = 0; - return ready; + return polylines.ready; }); } From 0d5ff05df9ea32ba10c521f42259aabad54e2531 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Fri, 17 Mar 2023 15:06:39 -0400 Subject: [PATCH 03/18] Update examples and CHANGES.md --- .../gallery/Custom Shaders Models.html | 42 +-- Apps/Sandcastle/gallery/HeadingPitchRoll.html | 239 +++++++++--------- .../gallery/Image-Based Lighting.html | 24 +- .../Sandcastle/gallery/LocalToFixedFrame.html | 48 ++-- .../Manually Controlled Animation.html | 70 ++--- .../development/3D Models Articulations.html | 24 +- .../development/3D Models Node Explorer.html | 24 +- .../gallery/development/3D Models.html | 26 +- .../development/Display Conditions.html | 28 +- .../gallery/development/Multiple Shadows.html | 42 +-- .../gallery/development/Shadows.html | 26 +- CHANGES.md | 17 +- .../Contributors/TestingGuide/README.md | 66 ++--- Documentation/CustomShaderGuide/README.md | 2 +- 14 files changed, 362 insertions(+), 316 deletions(-) diff --git a/Apps/Sandcastle/gallery/Custom Shaders Models.html b/Apps/Sandcastle/gallery/Custom Shaders Models.html index 5fe67b934e4f..5fb5960c87ed 100644 --- a/Apps/Sandcastle/gallery/Custom Shaders Models.html +++ b/Apps/Sandcastle/gallery/Custom Shaders Models.html @@ -393,26 +393,32 @@ }, ]; - function selectModel(url, customShader) { + async function selectModel(url, customShader) { viewer.scene.primitives.removeAll(); - const model = viewer.scene.primitives.add( - Cesium.Model.fromGltf({ - url: url, - customShader: customShader, - modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame( - position, - hpr, - Cesium.Ellipsoid.WGS84, - fixedFrameTransform - ), - }) - ); - - model.readyPromise.then(function (model) { - viewer.camera.flyToBoundingSphere(model.boundingSphere, { - duration: 0.5, + try { + const model = viewer.scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: url, + customShader: customShader, + modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame( + position, + hpr, + Cesium.Ellipsoid.WGS84, + fixedFrameTransform + ), + }) + ); + + const removeListener = model.readyEvent.addEventListener(() => { + viewer.camera.flyToBoundingSphere(model.boundingSphere, { + duration: 0.5, + }); + + removeListener(); }); - }); + } catch (error) { + console.log(`Error loading model: ${error}`); + } } Sandcastle.addToolbarMenu(demos); diff --git a/Apps/Sandcastle/gallery/HeadingPitchRoll.html b/Apps/Sandcastle/gallery/HeadingPitchRoll.html index 3863beec47ec..9629f74df1fb 100644 --- a/Apps/Sandcastle/gallery/HeadingPitchRoll.html +++ b/Apps/Sandcastle/gallery/HeadingPitchRoll.html @@ -125,132 +125,141 @@

    Loading...

    "west" ); - const planePrimitive = scene.primitives.add( - Cesium.Model.fromGltf({ - url: "../../SampleData/models/CesiumAir/Cesium_Air.glb", - modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame( - position, - hpRoll, - Cesium.Ellipsoid.WGS84, - fixedFrameTransform - ), - minimumPixelSize: 128, - }) - ); - - planePrimitive.readyPromise.then(function (model) { - // Play and loop all animations at half-speed - model.activeAnimations.addAll({ - multiplier: 0.5, - loop: Cesium.ModelAnimationLoop.REPEAT, - }); - - // Zoom to model - r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near); - controller.minimumZoomDistance = r * 0.5; - const center = model.boundingSphere.center; - const heading = Cesium.Math.toRadians(230.0); - const pitch = Cesium.Math.toRadians(-20.0); - hpRange.heading = heading; - hpRange.pitch = pitch; - hpRange.range = r * 50.0; - camera.lookAt(center, hpRange); - }); - - document.addEventListener("keydown", function (e) { - switch (e.keyCode) { - case 40: - if (e.shiftKey) { - // speed down - speed = Math.max(--speed, 1); - } else { - // pitch down - hpRoll.pitch -= deltaRadians; - if (hpRoll.pitch < -Cesium.Math.TWO_PI) { - hpRoll.pitch += Cesium.Math.TWO_PI; - } - } - break; - case 38: - if (e.shiftKey) { - // speed up - speed = Math.min(++speed, 100); - } else { - // pitch up - hpRoll.pitch += deltaRadians; - if (hpRoll.pitch > Cesium.Math.TWO_PI) { - hpRoll.pitch -= Cesium.Math.TWO_PI; - } - } - break; - case 39: - if (e.shiftKey) { - // roll right - hpRoll.roll += deltaRadians; - if (hpRoll.roll > Cesium.Math.TWO_PI) { - hpRoll.roll -= Cesium.Math.TWO_PI; - } - } else { - // turn right - hpRoll.heading += deltaRadians; - if (hpRoll.heading > Cesium.Math.TWO_PI) { - hpRoll.heading -= Cesium.Math.TWO_PI; - } - } - break; - case 37: - if (e.shiftKey) { - // roll left until - hpRoll.roll -= deltaRadians; - if (hpRoll.roll < 0.0) { - hpRoll.roll += Cesium.Math.TWO_PI; - } - } else { - // turn left - hpRoll.heading -= deltaRadians; - if (hpRoll.heading < 0.0) { - hpRoll.heading += Cesium.Math.TWO_PI; - } - } - break; - default: - } - }); - const headingSpan = document.getElementById("heading"); const pitchSpan = document.getElementById("pitch"); const rollSpan = document.getElementById("roll"); const speedSpan = document.getElementById("speed"); const fromBehind = document.getElementById("fromBehind"); - viewer.scene.preUpdate.addEventListener(function (scene, time) { - speedVector = Cesium.Cartesian3.multiplyByScalar( - Cesium.Cartesian3.UNIT_X, - speed / 10, - speedVector - ); - position = Cesium.Matrix4.multiplyByPoint( - planePrimitive.modelMatrix, - speedVector, - position - ); - pathPosition.addSample(Cesium.JulianDate.now(), position); - Cesium.Transforms.headingPitchRollToFixedFrame( - position, - hpRoll, - Cesium.Ellipsoid.WGS84, - fixedFrameTransform, - planePrimitive.modelMatrix + try { + const planePrimitive = scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: "../../SampleData/models/CesiumAir/Cesium_Air.glb", + modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame( + position, + hpRoll, + Cesium.Ellipsoid.WGS84, + fixedFrameTransform + ), + minimumPixelSize: 128, + }) ); - if (fromBehind.checked) { + planePrimitive.readyEvent.addEventListener(() => { + // Play and loop all animations at half-speed + planePrimitive.activeAnimations.addAll({ + multiplier: 0.5, + loop: Cesium.ModelAnimationLoop.REPEAT, + }); + // Zoom to model + r = + 2.0 * + Math.max( + planePrimitive.boundingSphere.radius, + camera.frustum.near + ); + controller.minimumZoomDistance = r * 0.5; const center = planePrimitive.boundingSphere.center; - hpRange.heading = hpRoll.heading; - hpRange.pitch = hpRoll.pitch; + const heading = Cesium.Math.toRadians(230.0); + const pitch = Cesium.Math.toRadians(-20.0); + hpRange.heading = heading; + hpRange.pitch = pitch; + hpRange.range = r * 50.0; camera.lookAt(center, hpRange); - } - }); + }); + + document.addEventListener("keydown", function (e) { + switch (e.keyCode) { + case 40: + if (e.shiftKey) { + // speed down + speed = Math.max(--speed, 1); + } else { + // pitch down + hpRoll.pitch -= deltaRadians; + if (hpRoll.pitch < -Cesium.Math.TWO_PI) { + hpRoll.pitch += Cesium.Math.TWO_PI; + } + } + break; + case 38: + if (e.shiftKey) { + // speed up + speed = Math.min(++speed, 100); + } else { + // pitch up + hpRoll.pitch += deltaRadians; + if (hpRoll.pitch > Cesium.Math.TWO_PI) { + hpRoll.pitch -= Cesium.Math.TWO_PI; + } + } + break; + case 39: + if (e.shiftKey) { + // roll right + hpRoll.roll += deltaRadians; + if (hpRoll.roll > Cesium.Math.TWO_PI) { + hpRoll.roll -= Cesium.Math.TWO_PI; + } + } else { + // turn right + hpRoll.heading += deltaRadians; + if (hpRoll.heading > Cesium.Math.TWO_PI) { + hpRoll.heading -= Cesium.Math.TWO_PI; + } + } + break; + case 37: + if (e.shiftKey) { + // roll left until + hpRoll.roll -= deltaRadians; + if (hpRoll.roll < 0.0) { + hpRoll.roll += Cesium.Math.TWO_PI; + } + } else { + // turn left + hpRoll.heading -= deltaRadians; + if (hpRoll.heading < 0.0) { + hpRoll.heading += Cesium.Math.TWO_PI; + } + } + break; + default: + } + }); + + viewer.scene.preUpdate.addEventListener(function (scene, time) { + speedVector = Cesium.Cartesian3.multiplyByScalar( + Cesium.Cartesian3.UNIT_X, + speed / 10, + speedVector + ); + position = Cesium.Matrix4.multiplyByPoint( + planePrimitive.modelMatrix, + speedVector, + position + ); + pathPosition.addSample(Cesium.JulianDate.now(), position); + Cesium.Transforms.headingPitchRollToFixedFrame( + position, + hpRoll, + Cesium.Ellipsoid.WGS84, + fixedFrameTransform, + planePrimitive.modelMatrix + ); + + if (fromBehind.checked) { + // Zoom to model + const center = planePrimitive.boundingSphere.center; + hpRange.heading = hpRoll.heading; + hpRange.pitch = hpRoll.pitch; + camera.lookAt(center, hpRange); + } + }); + } catch (error) { + console.log(`Error loading model: ${error}`); + } viewer.scene.preRender.addEventListener(function (scene, time) { headingSpan.innerHTML = Cesium.Math.toDegrees(hpRoll.heading).toFixed( diff --git a/Apps/Sandcastle/gallery/Image-Based Lighting.html b/Apps/Sandcastle/gallery/Image-Based Lighting.html index e65402087ffa..7c038f6ab58b 100644 --- a/Apps/Sandcastle/gallery/Image-Based Lighting.html +++ b/Apps/Sandcastle/gallery/Image-Based Lighting.html @@ -137,16 +137,16 @@ hpr ); - const model = viewer.scene.primitives.add( - Cesium.Model.fromGltf({ - url: modelURL, - modelMatrix: modelMatrix, - minimumPixelSize: 128, - }) - ); + try { + const model = viewer.scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: modelURL, + modelMatrix: modelMatrix, + minimumPixelSize: 128, + }) + ); - model.readyPromise - .then(function (model) { + model.readyEvent.addEventListener(() => { const camera = viewer.camera; // Zoom to model @@ -202,10 +202,10 @@ } } ); - }) - .catch(function (error) { - window.alert(error); }); + } catch (error) { + window.alert(`Error loading model: ${error}`); + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { diff --git a/Apps/Sandcastle/gallery/LocalToFixedFrame.html b/Apps/Sandcastle/gallery/LocalToFixedFrame.html index 1539df70542a..bfb66ea1707c 100644 --- a/Apps/Sandcastle/gallery/LocalToFixedFrame.html +++ b/Apps/Sandcastle/gallery/LocalToFixedFrame.html @@ -129,24 +129,28 @@

    Loading...

    const position = localFrames[i].pos; const converter = localFrames[i].converter; const comments = localFrames[i].comments; - const planePrimitive = scene.primitives.add( - Cesium.Model.fromGltf({ - url: "../../SampleData/models/CesiumAir/Cesium_Air.glb", - modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame( - position, - hpRoll, - Cesium.Ellipsoid.WGS84, - converter - ), - minimumPixelSize: 128, - }) - ); + try { + const planePrimitive = scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: "../../SampleData/models/CesiumAir/Cesium_Air.glb", + modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame( + position, + hpRoll, + Cesium.Ellipsoid.WGS84, + converter + ), + minimumPixelSize: 128, + }) + ); - primitives.push({ - primitive: planePrimitive, - converter: converter, - position: position, - }); + primitives.push({ + primitive: planePrimitive, + converter: converter, + position: position, + }); + } catch (error) { + console.log(`Error loading model: ${error}`); + } const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame( position, hprRollZero, @@ -178,14 +182,8 @@

    Loading...

    }); } - primitives[0].primitive.readyPromise.then(function (model) { - // Play and loop all animations at half-speed - model.activeAnimations.addAll({ - multiplier: 0.5, - loop: Cesium.ModelAnimationLoop.REPEAT, - }); - - // Zoom to model + primitives[0].primitive.readyEvent.addEventListener((model) => { + // Zoom to first model r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near); controller.minimumZoomDistance = r * 0.5; const center = model.boundingSphere.center; diff --git a/Apps/Sandcastle/gallery/Manually Controlled Animation.html b/Apps/Sandcastle/gallery/Manually Controlled Animation.html index 7ee0b66ed6a8..dca08d980e75 100644 --- a/Apps/Sandcastle/gallery/Manually Controlled Animation.html +++ b/Apps/Sandcastle/gallery/Manually Controlled Animation.html @@ -109,36 +109,25 @@ } // Add our model. - const modelPrimitive = viewer.scene.primitives.add( - Cesium.Model.fromGltf({ - url: "../../SampleData/models/CesiumMan/Cesium_Man.glb", - scale: 4, - }) - ); - const modelLabel = viewer.entities.add({ - position: position, - orientation: new Cesium.VelocityOrientationProperty(position), // Automatically set the model's orientation to the direction it's facing. - label: { - text: new Cesium.CallbackProperty(updateSpeedLabel, false), - font: "20px sans-serif", - showBackground: true, - distanceDisplayCondition: new Cesium.DistanceDisplayCondition( - 0.0, - 100.0 - ), - eyeOffset: new Cesium.Cartesian3(0, 7.2, 0), - }, - }); + try { + const modelPrimitive = viewer.scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: "../../SampleData/models/CesiumMan/Cesium_Man.glb", + scale: 4, + }) + ); - modelPrimitive.readyPromise.then(function (model) { - model.activeAnimations.addAll({ - loop: Cesium.ModelAnimationLoop.REPEAT, - animationTime: function (duration) { - return distance.getValue(viewer.clock.currentTime) / duration; - }, - multiplier: 0.25, + modelPrimitive.readyEvent.addEventListener(() => { + modelPrimitive.activeAnimations.addAll({ + loop: Cesium.ModelAnimationLoop.REPEAT, + animationTime: function (duration) { + return distance.getValue(viewer.clock.currentTime) / duration; + }, + multiplier: 0.25, + }); }); - const rot = new Cesium.Matrix3(); + + const rotation = new Cesium.Matrix3(); viewer.scene.preUpdate.addEventListener(function () { const time = viewer.clock.currentTime; const pos = position.getValue(time); @@ -148,10 +137,31 @@ pos, vel, viewer.scene.globe.ellipsoid, - rot + rotation + ); + Cesium.Matrix4.fromRotationTranslation( + rotation, + pos, + modelPrimitive.modelMatrix ); - Cesium.Matrix4.fromRotationTranslation(rot, pos, model.modelMatrix); }); + } catch (error) { + window.alert(error); + } + + const modelLabel = viewer.entities.add({ + position: position, + orientation: new Cesium.VelocityOrientationProperty(position), // Automatically set the model's orientation to the direction it's facing. + label: { + text: new Cesium.CallbackProperty(updateSpeedLabel, false), + font: "20px sans-serif", + showBackground: true, + distanceDisplayCondition: new Cesium.DistanceDisplayCondition( + 0.0, + 100.0 + ), + eyeOffset: new Cesium.Cartesian3(0, 7.2, 0), + }, }); viewer.trackedEntity = modelLabel; modelLabel.viewFrom = new Cesium.Cartesian3(-30.0, -10.0, 10.0); diff --git a/Apps/Sandcastle/gallery/development/3D Models Articulations.html b/Apps/Sandcastle/gallery/development/3D Models Articulations.html index d276ccb100e4..97a795c2b986 100644 --- a/Apps/Sandcastle/gallery/development/3D Models Articulations.html +++ b/Apps/Sandcastle/gallery/development/3D Models Articulations.html @@ -107,16 +107,16 @@ new Cesium.HeadingPitchRoll() ); - const model = scene.primitives.add( - Cesium.Model.fromGltf({ - url: modelUrl, - modelMatrix: modelMatrix, - minimumPixelSize: 128, - }) - ); + try { + const model = scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: modelUrl, + modelMatrix: modelMatrix, + minimumPixelSize: 128, + }) + ); - model.readyPromise - .then(function (model) { + model.readyEvent.addEventListener(() => { const camera = viewer.camera; // Zoom to model @@ -176,10 +176,10 @@ }; }); viewModel.selectedArticulation = viewModel.articulations[0]; - }) - .catch(function (error) { - console.error(error); }); + } catch (error) { + console.log(`Error loading model: ${error}`); + } //Sandcastle_End }; diff --git a/Apps/Sandcastle/gallery/development/3D Models Node Explorer.html b/Apps/Sandcastle/gallery/development/3D Models Node Explorer.html index 84165f2d7a49..7c13672c0299 100644 --- a/Apps/Sandcastle/gallery/development/3D Models Node Explorer.html +++ b/Apps/Sandcastle/gallery/development/3D Models Node Explorer.html @@ -302,16 +302,16 @@ new Cesium.HeadingPitchRoll() ); - const model = scene.primitives.add( - Cesium.Model.fromGltf({ - url: modelUrl, - modelMatrix: modelMatrix, - minimumPixelSize: 128, - }) - ); + try { + const model = scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: modelUrl, + modelMatrix: modelMatrix, + minimumPixelSize: 128, + }) + ); - model.readyPromise - .then(function (model) { + model.readyEvent.addEventListener(() => { const camera = viewer.camera; // Zoom to model @@ -363,10 +363,10 @@ new Cesium.Matrix4() ); }); - }) - .catch(function (error) { - window.alert(error); }); + } catch (error) { + window.alert(error); + } //Sandcastle_End }; diff --git a/Apps/Sandcastle/gallery/development/3D Models.html b/Apps/Sandcastle/gallery/development/3D Models.html index d182236569ff..6fc693c9a412 100644 --- a/Apps/Sandcastle/gallery/development/3D Models.html +++ b/Apps/Sandcastle/gallery/development/3D Models.html @@ -160,7 +160,7 @@ model.colorBlendAmount = Number(newValue); }); - function createModel(url, height, heading, pitch, roll) { + async function createModel(url, height, heading, pitch, roll) { height = Cesium.defaultValue(height, 0.0); heading = Cesium.defaultValue(heading, 0.0); pitch = Cesium.defaultValue(pitch, 0.0); @@ -178,16 +178,16 @@ ); scene.primitives.removeAll(); // Remove previous model - model = scene.primitives.add( - Cesium.Model.fromGltf({ - url: url, - modelMatrix: modelMatrix, - minimumPixelSize: 128, - }) - ); + try { + model = scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: url, + modelMatrix: modelMatrix, + minimumPixelSize: 128, + }) + ); - model.readyPromise - .then(function (model) { + model.readyEvent.addEventListener(() => { model.color = Cesium.Color.fromAlpha( getColor(viewModel.color), Number(viewModel.alpha) @@ -218,10 +218,10 @@ center, new Cesium.HeadingPitchRange(heading, pitch, r * 2.0) ); - }) - .catch(function (error) { - window.alert(error); }); + } catch (error) { + window.alert(error); + } } const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); diff --git a/Apps/Sandcastle/gallery/development/Display Conditions.html b/Apps/Sandcastle/gallery/development/Display Conditions.html index f3838c402087..1c38d42e364d 100644 --- a/Apps/Sandcastle/gallery/development/Display Conditions.html +++ b/Apps/Sandcastle/gallery/development/Display Conditions.html @@ -78,7 +78,7 @@ ); } - function addPointAndModel() { + async function addPointAndModel() { Sandcastle.declare(addPointAndModel); const position = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883); @@ -94,16 +94,22 @@ ), }); - scene.primitives.add( - Cesium.Model.fromGltf({ - url: "../../SampleData/models/GroundVehicle/GroundVehicle.glb", - modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(position), - distanceDisplayCondition: new Cesium.DistanceDisplayCondition( - 0.0, - 250.5 - ), - }) - ); + try { + scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: "../../SampleData/models/GroundVehicle/GroundVehicle.glb", + modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame( + position + ), + distanceDisplayCondition: new Cesium.DistanceDisplayCondition( + 0.0, + 250.5 + ), + }) + ); + } catch (error) { + window.alert(error); + } } Sandcastle.addToolbarMenu([ diff --git a/Apps/Sandcastle/gallery/development/Multiple Shadows.html b/Apps/Sandcastle/gallery/development/Multiple Shadows.html index a87df91fcc86..e72b532ec680 100644 --- a/Apps/Sandcastle/gallery/development/Multiple Shadows.html +++ b/Apps/Sandcastle/gallery/development/Multiple Shadows.html @@ -72,28 +72,30 @@ shadowMap.enabled = true; - const model = scene.primitives.add( - Cesium.Model.fromGltf({ - url: - "../../SampleData/models/ShadowTester/Shadow_Tester_Point.glb", - modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame( - center, - new Cesium.HeadingPitchRoll(heading, 0.0, 0.0) - ), - }) - ); - - model.readyPromise - .then(function (model) { - // Play and loop all animations at half-speed - model.activeAnimations.addAll({ - multiplier: 0.5, - loop: Cesium.ModelAnimationLoop.REPEAT, + (async () => { + try { + const model = scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: + "../../SampleData/models/ShadowTester/Shadow_Tester_Point.glb", + modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame( + center, + new Cesium.HeadingPitchRoll(heading, 0.0, 0.0) + ), + }) + ); + + model.readyEvent.addEventListener(() => { + // Play and loop all animations at half-speed + model.activeAnimations.addAll({ + multiplier: 0.5, + loop: Cesium.ModelAnimationLoop.REPEAT, + }); }); - }) - .catch(function (error) { + } catch (error) { window.alert(error); - }); + } + })(); return shadowMap; } diff --git a/Apps/Sandcastle/gallery/development/Shadows.html b/Apps/Sandcastle/gallery/development/Shadows.html index 67a7ac804203..f6d39e4adcbe 100644 --- a/Apps/Sandcastle/gallery/development/Shadows.html +++ b/Apps/Sandcastle/gallery/development/Shadows.html @@ -913,32 +913,32 @@ }); } - function createModel(url, origin) { + async function createModel(url, origin) { const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame( origin, new Cesium.HeadingPitchRoll() ); - const model = scene.primitives.add( - Cesium.Model.fromGltf({ - url: url, - modelMatrix: modelMatrix, - }) - ); + try { + const model = scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: url, + modelMatrix: modelMatrix, + }) + ); - model.readyPromise - .then(function (model) { + model.readyEvent.addEventListener(() => { // Play and loop all animations at half-speed model.activeAnimations.addAll({ multiplier: 0.5, loop: Cesium.ModelAnimationLoop.REPEAT, }); - }) - .catch(function (error) { - window.alert(error); }); - return model; + return model; + } catch (error) { + window.alert(error); + } } function createBoxRTC(origin) { diff --git a/CHANGES.md b/CHANGES.md index c3ffcc9b3e98..ee6040c27b50 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,7 +6,7 @@ ##### Additions :tada: -- Added `ArcGisMapServerImageryProvider.fromUrl`, `ArcGISTiledElevationTerrainProvider.fromUrl`, `BingMapsImageryProvider.fromUrl`, `CesiumTerrainProvider.fromUrl`, `GoogleEarthEnterpriseMetadata.fromUrl`, `GoogleEarthEnterpriseImageryProvider.fromMetadata`, `GoogleEarthEnterpriseMapsProvider.fromUrl`, `GoogleEarthEnterpriseTerrainProvider.fromMetadata`, `ImageryLayer.fromProviderAsync`, `IonImageryProvider.fromAssetId`, `SingleTileImageryProvider.fromUrl`, `Terrain`, `TileMapServiceImageryProvider.fromUrl`, `VRTheWorldTerrainProvider.fromUrl`, and `createWorldTerrainAsync` for better async flow and error handling. +- Added `ArcGisMapServerImageryProvider.fromUrl`, `ArcGISTiledElevationTerrainProvider.fromUrl`, `BingMapsImageryProvider.fromUrl`, `CesiumTerrainProvider.fromUrl`, `GoogleEarthEnterpriseMetadata.fromUrl`, `GoogleEarthEnterpriseImageryProvider.fromMetadata`, `GoogleEarthEnterpriseMapsProvider.fromUrl`, `GoogleEarthEnterpriseTerrainProvider.fromMetadata`, `ImageryLayer.fromProviderAsync`, `IonImageryProvider.fromAssetId`, `SingleTileImageryProvider.fromUrl`, `Terrain`, `TileMapServiceImageryProvider.fromUrl`, `VRTheWorldTerrainProvider.fromUrl`, `createWorldTerrainAsync`, `Model.fromGltfAsync`, , `Model.readyEvent`, `Model.errorEvent`, and `Model.texturesReadyEvent` for better async flow and error handling. ##### Fixes :wrench: @@ -45,6 +45,21 @@ - `GoogleEarthEnterpriseTerrainProvider` constructor parameters `options.url` and `options.metadata`, `GoogleEarthEnterpriseTerrainProvider.ready`, and `GoogleEarthEnterpriseTerrainProvider.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `GoogleEarthEnterpriseTerrainProvider.fromMetadata` instead. - `VRTheWorldTerrainProvider` constructor parameter `options.url`, `VRTheWorldTerrainProvider.ready`, and `VRTheWorldTerrainProvider.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `VRTheWorldTerrainProvider.fromUrl` instead. - `createWorldTerrain` was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use createWorldTerrainAsync instead. +- `Model.fromGltf`, `Model.readyPromise`, and `Model.texturesLoadedPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `Model.fromGltfAsync`, `Model.readyEvent`, `Model.errorEvent`, and `Model.texturesReadyEvent` instead. For example: + + ```js + try { + const model = await Cesium.Model.fromGltfAsync({ + url: "../../SampleData/models/CesiumMan/Cesium_Man.glb", + }); + viewer.scene.primitives.add(model); + model.readyEvent.addEventListener(() => { + // model is ready for rendering + }); + } catch (error) { + console.log(`Failed to load model. ${error}`); + } + ``` #### @cesium/widgets diff --git a/Documentation/Contributors/TestingGuide/README.md b/Documentation/Contributors/TestingGuide/README.md index 2488dae152ca..fdf75033ccb8 100644 --- a/Documentation/Contributors/TestingGuide/README.md +++ b/Documentation/Contributors/TestingGuide/README.md @@ -678,9 +678,18 @@ Make external requests that assume the tests are being used with an Internet con (For an introduction to promises, see [JavaScript Promises - There and back again](http://www.html5rocks.com/en/tutorials/es6/promises/)). -For asynchronous testing, Jasmine's `it` function uses a `done` callback. For better integration with CesiumJS's asynchronous patterns, CesiumJS replaces `it` with a function that can return promises. +Jasmine also has support for running specs that require testing asynchronous operations. The functions that you pass to `beforeAll`, `afterAll`, `beforeEach`, `afterEach`, and `it` can be declared `async`. These functions can also return promises. There are also cases where asynchronous functions that explicitly return promises should be tested. See the [Asynchronous Work tutorial](https://jasmine.github.io/tutorials/async) for more information. -Here is a simplified example of a test from [ModelSpec.js](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/Model/ModelSpec.js): +Here is a simplified example of `beforeAll` from [sampleTerrainSpec.js](https://github.com/CesiumGS/cesium/blob/main/packages/engine/Specs/Core/sampleTerrainSpec.js): + +```javascript +let worldTerrain; +beforeAll(async function () { + worldTerrain = await createWorldTerrainAsync(); +}); +``` + +Here is a simplified example of a test from [ModelSpec.js](https://github.com/CesiumGS/cesium/blob/main/packages/engine/Specs/Scene/Model/ModelSpec.js): ```javascript const modelUrl = "./Data/Models/glTF-2.0/Box/glTF/Box.gltf"; @@ -694,59 +703,50 @@ afterAll(function () { scene.destroyForSpecs(); }); -it("renders glTF model", function () { - return loadAndZoomToModel({ gltf: modelUrl }, scene).then(function (model) { - expect(scene).toRenderAndCall(function (rgba) { - expect(rgba[0]).toBeGreaterThan(0); - expect(rgba[1]).toBeGreaterThan(0); - expect(rgba[2]).toBeGreaterThan(0); - expect(rgba[3]).toBe(255); - }); +it("renders glTF model", async function () { + const model = await loadAndZoomToModelAsync({ gltf: modelUrl }, scene); + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba[0]).toBeGreaterThan(0); + expect(rgba[1]).toBeGreaterThan(0); + expect(rgba[2]).toBeGreaterThan(0); + expect(rgba[3]).toBe(255); }); }); ``` -Given a model's url and other options, [`loadAndZoomToModel`](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/Model/loadAndZoomToModel.js) loads a model, configures the camera, and returns a promise that resolves when a model's `readyPromise` resolves. +Given a model's url and other options, [`loadAndZoomToModelAsync`](https://github.com/CesiumGS/cesium/blob/main/paclages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js) loads a model, configures the camera, and returns a promise that resolves when a model is ready for rendering. -Since loading a model requires asynchronous requests and creating WebGL resources that may be spread over several frames, CesiumJS's [`pollToPromise`](https://github.com/CesiumGS/cesium/blob/main/Specs/pollToPromise.js) is used to return a promise that resolves when the model is ready, which occurs by rendering the scene in an implicit loop (hence the name "poll") until `model.readyPromise` resolves or the `timeout` is reached. `loadAndZoomToModel` uses `pollToPromise` to wait until the model is finished loading. +Since loading a model requires asynchronous requests and creating WebGL resources that may be spread over several frames, CesiumJS's [`pollToPromise`](https://github.com/CesiumGS/cesium/blob/main/Specs/pollToPromise.js) is used to return a promise that resolves when the model is ready, which occurs by rendering the scene in an implicit loop (hence the name "poll") until `model.ready` is `true` or the `timeout` is reached. -`pollToPromise` is also used in many places where a test needs to wait for an asynchronous event before testing its expectations. Here is an excerpt from [BillboardCollectionSpec.js](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/BillboardCollectionSpec.js): +`pollToPromise` is also used in many places where a test needs to wait for an asynchronous event before testing its expectations. Here is an excerpt from [BillboardCollectionSpec.js](https://github.com/CesiumGS/cesium/blob/main/packages/engine/Specs/Scene/BillboardCollectionSpec.js): ```javascript -it("can create a billboard using a URL", function () { +it("can create a billboard using a URL", async function () { const b = billboards.add({ image: "./Data/Images/Green.png", }); expect(b.ready).toEqual(false); - return pollToPromise(function () { + await pollToPromise(function () { return b.ready; - }).then(function () { - expect(scene).toRender([0, 255, 0, 255]); }); + + expect(scene).toRender([0, 255, 0, 255]); }); ``` -Here a billboard is loaded using a url to image. Internally, `Billboard` makes an asynchronous request for the image and then sets its `ready` property to `true`. The function passed to `pollToPromise` just returns the value of `ready`; it does not need to render the scene to progressively complete the request like `Model`. Finally, the resolve function (passed to `then`) verifies that the billboard is green. +Here a billboard is loaded using a url to image. Internally, `Billboard` makes an asynchronous request for the image and then sets its `ready` property to `true`. The function passed to `pollToPromise` just returns the value of `ready`; it does not need to render the scene to progressively complete the request like `Model`. Finally, the test verifies that the billboard is green. -To test if a promises rejects, we call `fail` in the resolve function and put the expectation in the reject function. Here is an excerpt from [ArcGisMapServerImageryProviderSpec.js](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/ArcGisMapServerImageryProviderSpec.js): +To test if a promises rejects, we use `expectAsync` and provide the expected error type and message. Here is an excerpt from [ArcGISTiledElevationTerrainProviderSpec.js](https://github.com/CesiumGS/cesium/blob/main/packages/engine/Specs/Core/ArcGISTiledElevationTerrainProviderSpec.js): ```javascript -it("rejects readyPromise on error", function () { - const baseUrl = "//tiledArcGisMapServer.invalid"; - - const provider = new ArcGisMapServerImageryProvider({ - url: baseUrl, - }); +it("fromUrl throws if the SRS is not supported", async function () { + const baseUrl = "made/up/url"; + metadata.spatialReference.latestWkid = 1234; - return provider.readyPromise - .then(function () { - fail("should not resolve"); - }) - .catch(function (e) { - expect(e.message).toContain(baseUrl); - expect(provider.ready).toBe(false); - }); + await expectAsync( + ArcGISTiledElevationTerrainProvider.fromUrl(baseUrl) + ).toBeRejectedWithError(RuntimeError, "Invalid spatial reference"); }); ``` diff --git a/Documentation/CustomShaderGuide/README.md b/Documentation/CustomShaderGuide/README.md index 7a95e34cac73..681559dbdf39 100644 --- a/Documentation/CustomShaderGuide/README.md +++ b/Documentation/CustomShaderGuide/README.md @@ -73,7 +73,7 @@ const tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ })); // Applying to a model directly -const model = Cesium.Model.fromGltf({, +const model = await Cesium.Model.fromGltfAsync({, url: "http://example.com/model.gltf", customShader: customShader }); From 0a498caa36c5dcf533daebbd04933bff0b05bb28 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Mon, 20 Mar 2023 11:57:06 -0400 Subject: [PATCH 04/18] Deprecate Cesium3DTileset ready and readyPromise --- .../gallery/3D Tiles Adjust Height.html | 41 +- Apps/Sandcastle/gallery/3D Tiles BIM.html | 52 +- .../3D Tiles Batch Table Hierarchy.html | 32 +- CHANGES.md | 20 +- .../PerformanceTestingGuide/README.md | 25 +- .../Contributors/TestingGuide/README.md | 10 + Specs/Cesium3DTilesTester.js | 22 +- packages/engine/Source/Core/IonResource.js | 12 +- .../DataSources/Cesium3DTilesetVisualizer.js | 63 +- .../Source/Scene/Cesium3DTileStyleEngine.js | 2 +- .../engine/Source/Scene/Cesium3DTileset.js | 681 +++++++++++------- packages/engine/Source/Scene/I3SLayer.js | 63 +- .../engine/Source/Scene/createOsmBuildings.js | 6 + .../Source/Scene/createOsmBuildingsAsync.js | 84 +++ .../Cesium3DTilesetVisualizerSpec.js | 93 +-- .../engine/Specs/Scene/Cesium3DTilesetSpec.js | 358 ++++----- packages/engine/Specs/Scene/I3SLayerSpec.js | 80 +- .../Specs/Scene/Vector3DTileContentSpec.js | 8 +- .../Cesium3DTilesInspectorViewModel.js | 3 +- packages/widgets/Source/Viewer/Viewer.js | 3 +- .../Cesium3DTilesInspectorViewModelSpec.js | 38 +- packages/widgets/Specs/Viewer/ViewerSpec.js | 239 +++--- 22 files changed, 1035 insertions(+), 900 deletions(-) create mode 100644 packages/engine/Source/Scene/createOsmBuildingsAsync.js diff --git a/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html b/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html index c8e8f6c139ac..31de2f9fcbd1 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html +++ b/Apps/Sandcastle/gallery/3D Tiles Adjust Height.html @@ -60,8 +60,6 @@ shadows: true, }); - viewer.scene.globe.depthTestAgainstTerrain = true; - const viewModel = { height: 0, }; @@ -71,31 +69,30 @@ const toolbar = document.getElementById("toolbar"); Cesium.knockout.applyBindings(viewModel, toolbar); - const tileset = new Cesium.Cesium3DTileset({ - url: "../../SampleData/Cesium3DTiles/Tilesets/Tileset/tileset.json", - }); - - tileset.readyPromise - .then(function (tileset) { - viewer.scene.primitives.add(tileset); - viewer.zoomTo( - tileset, - new Cesium.HeadingPitchRange( - 0.0, - -0.5, - tileset.boundingSphere.radius * 2.0 - ) - ); - }) - .catch(function (error) { - console.log(error); - }); + let tileset; + try { + tileset = await Cesium.Cesium3DTileset.fromUrl( + "../../SampleData/Cesium3DTiles/Tilesets/Tileset/tileset.json" + ); + viewer.scene.primitives.add(tileset); + viewer.scene.globe.depthTestAgainstTerrain = true; + viewer.zoomTo( + tileset, + new Cesium.HeadingPitchRange( + 0.0, + -0.5, + tileset.boundingSphere.radius * 2.0 + ) + ); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } Cesium.knockout .getObservable(viewModel, "height") .subscribe(function (height) { height = Number(height); - if (isNaN(height)) { + if (isNaN(height) || !Cesium.defined(tileset)) { return; } diff --git a/Apps/Sandcastle/gallery/3D Tiles BIM.html b/Apps/Sandcastle/gallery/3D Tiles BIM.html index 2f403e730916..06df8cba02b3 100644 --- a/Apps/Sandcastle/gallery/3D Tiles BIM.html +++ b/Apps/Sandcastle/gallery/3D Tiles BIM.html @@ -43,29 +43,6 @@ "2022-08-01T00:00:00Z" ); - const tileset = scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(1240402), - }) - ); - - tileset.readyPromise - .then(function (tileset) { - viewer.zoomTo( - tileset, - new Cesium.HeadingPitchRange( - 0.5, - -0.2, - tileset.boundingSphere.radius * 4.0 - ) - ); - }) - .catch(function (error) { - console.log(error); - }); - - tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.REPLACE; - let selectedFeature; let picking = false; @@ -209,13 +186,30 @@ } } - tileset.tileLoad.addEventListener(function (tile) { - processTileFeatures(tile, loadFeature); - }); + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(1240402); + scene.primitives.add(tileset); + + viewer.zoomTo( + tileset, + new Cesium.HeadingPitchRange( + 0.5, + -0.2, + tileset.boundingSphere.radius * 4.0 + ) + ); + + tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.REPLACE; + tileset.tileLoad.addEventListener(function (tile) { + processTileFeatures(tile, loadFeature); + }); - tileset.tileUnload.addEventListener(function (tile) { - processTileFeatures(tile, unloadFeature); - }); + tileset.tileUnload.addEventListener(function (tile) { + processTileFeatures(tile, unloadFeature); + }); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { diff --git a/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html b/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html index de0497ca1f44..5e7a1731d76d 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html +++ b/Apps/Sandcastle/gallery/3D Tiles Batch Table Hierarchy.html @@ -95,13 +95,27 @@ const viewer = new Cesium.Viewer("cesiumContainer"); viewer.clock.currentTime = new Cesium.JulianDate(2457522.154792); - const tileset = new Cesium.Cesium3DTileset({ - url: - "../../SampleData/Cesium3DTiles/Hierarchy/BatchTableHierarchy/tileset.json", - }); + let tileset; + try { + tileset = await Cesium.Cesium3DTileset.fromUrl( + "../../SampleData/Cesium3DTiles/Hierarchy/BatchTableHierarchy/tileset.json" + ); + + viewer.scene.primitives.add(tileset); + viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.3, 0.0)); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } - viewer.scene.primitives.add(tileset); - viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.3, 0.0)); + function setStyle(style) { + return function () { + if (!Cesium.defined(tileset)) { + return; + } + + tileset.style = new Cesium.Cesium3DTileStyle(style); + }; + } const styles = []; function addStyle(name, style) { @@ -167,12 +181,6 @@ }, }); - function setStyle(style) { - return function () { - tileset.style = new Cesium.Cesium3DTileStyle(style); - }; - } - const styleOptions = []; for (let i = 0; i < styles.length; ++i) { const style = styles[i]; diff --git a/CHANGES.md b/CHANGES.md index ee6040c27b50..9dff17ef1645 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,11 +2,24 @@ ### 1.104 - 2023-04-03 +#### Major Announcements :loudspeaker: + +- Starting with CesiumJS 1.104 The `readyPromise` pattern has been deprecated across the API. It will be removed in CesiumJS 1.107. This has been done to facilitate better asynchronous flow and error management. For example: + +```js +try { + const tileset = await Cesium.Cesium3DTileset.fromUrl(url); + viewer.scene.primitives.add(tileset); +} catch (error) { + console.log(`Failed to load tileset: ${error}`); +} +``` + #### @cesium/engine ##### Additions :tada: -- Added `ArcGisMapServerImageryProvider.fromUrl`, `ArcGISTiledElevationTerrainProvider.fromUrl`, `BingMapsImageryProvider.fromUrl`, `CesiumTerrainProvider.fromUrl`, `GoogleEarthEnterpriseMetadata.fromUrl`, `GoogleEarthEnterpriseImageryProvider.fromMetadata`, `GoogleEarthEnterpriseMapsProvider.fromUrl`, `GoogleEarthEnterpriseTerrainProvider.fromMetadata`, `ImageryLayer.fromProviderAsync`, `IonImageryProvider.fromAssetId`, `SingleTileImageryProvider.fromUrl`, `Terrain`, `TileMapServiceImageryProvider.fromUrl`, `VRTheWorldTerrainProvider.fromUrl`, `createWorldTerrainAsync`, `Model.fromGltfAsync`, , `Model.readyEvent`, `Model.errorEvent`, and `Model.texturesReadyEvent` for better async flow and error handling. +- Added `ArcGisMapServerImageryProvider.fromUrl`, `ArcGISTiledElevationTerrainProvider.fromUrl`, `BingMapsImageryProvider.fromUrl`, `CesiumTerrainProvider.fromUrl`, `GoogleEarthEnterpriseMetadata.fromUrl`, `GoogleEarthEnterpriseImageryProvider.fromMetadata`, `GoogleEarthEnterpriseMapsProvider.fromUrl`, `GoogleEarthEnterpriseTerrainProvider.fromMetadata`, `ImageryLayer.fromProviderAsync`, `IonImageryProvider.fromAssetId`, `SingleTileImageryProvider.fromUrl`, `Terrain`, `TileMapServiceImageryProvider.fromUrl`, `VRTheWorldTerrainProvider.fromUrl`, `createWorldTerrainAsync`, `Cesium3DTileset.fromUrl`, `createOsmBuildingsAsync`, `Model.fromGltfAsync`, , `Model.readyEvent`, `Model.errorEvent`, and `Model.texturesReadyEvent` for better async flow and error handling. ##### Fixes :wrench: @@ -44,9 +57,10 @@ - `GoogleEarthEnterpriseMetadata` constructor parameter `options.url` and `GoogleEarthEnterpriseMetadata.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `GoogleEarthEnterpriseMetadata.fromUrl` instead. - `GoogleEarthEnterpriseTerrainProvider` constructor parameters `options.url` and `options.metadata`, `GoogleEarthEnterpriseTerrainProvider.ready`, and `GoogleEarthEnterpriseTerrainProvider.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `GoogleEarthEnterpriseTerrainProvider.fromMetadata` instead. - `VRTheWorldTerrainProvider` constructor parameter `options.url`, `VRTheWorldTerrainProvider.ready`, and `VRTheWorldTerrainProvider.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `VRTheWorldTerrainProvider.fromUrl` instead. -- `createWorldTerrain` was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use createWorldTerrainAsync instead. +- `createWorldTerrain` was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use `createWorldTerrainAsync` instead. +- `Cesium3DTileset` constructor parameter `options.url`, `Cesium3DTileset.ready`, and `Cesium3DTileset.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `Cesium3DTileset.fromUrl` instead. +- `createOsmBuildings` was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use `createOsmBuildingsAsync` instead. - `Model.fromGltf`, `Model.readyPromise`, and `Model.texturesLoadedPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `Model.fromGltfAsync`, `Model.readyEvent`, `Model.errorEvent`, and `Model.texturesReadyEvent` instead. For example: - ```js try { const model = await Cesium.Model.fromGltfAsync({ diff --git a/Documentation/Contributors/PerformanceTestingGuide/README.md b/Documentation/Contributors/PerformanceTestingGuide/README.md index 9244c10bc869..decea410bfee 100644 --- a/Documentation/Contributors/PerformanceTestingGuide/README.md +++ b/Documentation/Contributors/PerformanceTestingGuide/README.md @@ -127,37 +127,26 @@ class Timer { To time how long it takes for the tileset.json to be fetched and the `Cesium3DTileset` to be initialized (not including the tile content), start -the timer just before creating the tileset, and stop it in the `readyPromise` +the timer just before creating the tileset with `Cesium3DTileset.fromUrl`, and stop it after the asynchronous function has completed. ```js tilesetTimer.start(); -const tileset = new Cesium.Cesium3DTileset({ - url, -}); -tileset.maximumScreenSpaceError = 4; -// and any other initialization code - -viewer.scene.primitives.add(tileset); - -tileset.readyPromise.then(() => { - tilesetTimer.stop(); - tilesetTimer.print(); -}); +const tileset = await Cesium.Cesium3DTileset.fromUrl(url); +tilesetTimer.stop(); +tilesetTimer.print(); ``` ### How to measure tile load time To time how long it takes for all the tiles in the current camera view to load -(not including the tileset load time), start the timer in the ready promise -and stop it in the `initialTilesLoaded` event handler. This event is used +(not including the tileset load time), after the tileset has been created with `Cesium3DTileset.fromUrl` and stop it in the `initialTilesLoaded` event handler. This event is used because we only care about our initial fixed camera view. `allTilesLoaded`, in contrast, may trigger multiple times if the camera moves, which is undesireable here. ```js -tileset.readyPromise.then(() => { - tileTimer.start(); -}); +const tileset = await Cesium.Cesium3DTileset.fromUrl(url); +tileTimer.start(); // This callback is fired the first time that all the visible tiles are loaded. // It indicates the end of the test. diff --git a/Documentation/Contributors/TestingGuide/README.md b/Documentation/Contributors/TestingGuide/README.md index fdf75033ccb8..6b367b69ebdc 100644 --- a/Documentation/Contributors/TestingGuide/README.md +++ b/Documentation/Contributors/TestingGuide/README.md @@ -750,6 +750,16 @@ it("fromUrl throws if the SRS is not supported", async function () { }); ``` +Since developer errors are removed for release builds, CesiumJS's `toBeRejectedWithDeveloperError` matcher is used to verify asynchronous Developer Errors. Here is an excerpt from [Cesium3DTilesetSpec.js](https://github.com/CesiumGS/cesium/blob/main/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js): + +```javascript +it("fromUrl throws without url", async function () { + await expectAsync(Cesium3DTileset.fromUrl()).toBeRejectedWithDeveloperError( + "url is required, actual value was undefined" + ); +}); +``` + ### Mocks To isolate testing, mock objects can be used to simulate real objects. Here is an excerpt from [SceneSpec.js](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/SceneSpec.js); diff --git a/Specs/Cesium3DTilesTester.js b/Specs/Cesium3DTilesTester.js index 04318ceeadf7..b52d0e73cea8 100644 --- a/Specs/Cesium3DTilesTester.js +++ b/Specs/Cesium3DTilesTester.js @@ -102,27 +102,19 @@ Cesium3DTilesTester.waitForTilesLoaded = function (scene, tileset) { }); }; -Cesium3DTilesTester.waitForReady = function (scene, tileset) { - return pollToPromise(function () { - scene.renderForSpecs(); - return tileset.ready; - }).then(function () { - return tileset; - }); -}; - -Cesium3DTilesTester.loadTileset = function (scene, url, options) { +Cesium3DTilesTester.loadTileset = async function (scene, url, options) { options = defaultValue(options, {}); - options.url = url; options.cullRequestsWhileMoving = defaultValue( options.cullRequestsWhileMoving, false ); + + const tileset = await Cesium3DTileset.fromUrl(url, options); + // Load all visible tiles - const tileset = scene.primitives.add(new Cesium3DTileset(options)); - return tileset.readyPromise.then(function () { - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); - }); + scene.primitives.add(tileset); + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + return tileset; }; Cesium3DTilesTester.createContentForMockTile = async function ( diff --git a/packages/engine/Source/Core/IonResource.js b/packages/engine/Source/Core/IonResource.js index 137be3899ee1..91ce926c34a9 100644 --- a/packages/engine/Source/Core/IonResource.js +++ b/packages/engine/Source/Core/IonResource.js @@ -16,7 +16,7 @@ import RuntimeError from "./RuntimeError.js"; * @augments Resource * * @param {object} endpoint The result of the Cesium ion asset endpoint service. - * @param {Resource} endpointResource The resource used to retreive the endpoint. + * @param {Resource} endpointResource The resource used to retrieve the endpoint. * * @see Ion * @see IonImageryProvider @@ -87,8 +87,14 @@ if (defined(Object.create)) { * @returns {Promise} A Promise to am instance representing the Cesium ion Asset. * * @example - * //Load a Cesium3DTileset with asset ID of 124624234 - * viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url: Cesium.IonResource.fromAssetId(124624234) })); + * // Load a Cesium3DTileset with asset ID of 124624234 + * try { + * const resource = await Cesium.IonResource.fromAssetId(124624234); + * const tileset = await Cesium.Cesium3DTileset.fromUrl(resource); + * scene.primitives.add(tileset); + * } catch (error) { + * console.error(`Error creating tileset: ${error}`); + * } * * @example * //Load a CZML file with asset ID of 10890 diff --git a/packages/engine/Source/DataSources/Cesium3DTilesetVisualizer.js b/packages/engine/Source/DataSources/Cesium3DTilesetVisualizer.js index f0c5a9f794df..950c365b847d 100644 --- a/packages/engine/Source/DataSources/Cesium3DTilesetVisualizer.js +++ b/packages/engine/Source/DataSources/Cesium3DTilesetVisualizer.js @@ -65,7 +65,7 @@ Cesium3DTilesetVisualizer.prototype.update = function (time) { const tilesetGraphics = entity._tileset; let resource; - let tilesetData = tilesetHash[entity.id]; + const tilesetData = tilesetHash[entity.id]; const show = entity.isShowing && entity.isAvailable(time) && @@ -86,28 +86,21 @@ Cesium3DTilesetVisualizer.prototype.update = function (time) { continue; } - let tileset = defined(tilesetData) + const tileset = defined(tilesetData) ? tilesetData.tilesetPrimitive : undefined; - if (!defined(tileset) || resource.url !== tilesetData.url) { + if (!defined(tilesetData) || resource.url !== tilesetData.url) { if (defined(tileset)) { primitives.removeAndDestroy(tileset); - delete tilesetHash[entity.id]; } - tileset = new Cesium3DTileset({ - url: resource, - }); - tileset.id = entity; - primitives.add(tileset); - - tilesetData = { - tilesetPrimitive: tileset, - url: resource.url, - loadFail: false, - }; - tilesetHash[entity.id] = tilesetData; - - checkLoad(tileset, entity, tilesetHash); + + delete tilesetHash[entity.id]; + + createTileset(resource, tilesetHash, entity, primitives); + } + + if (!defined(tileset)) { + continue; } tileset.show = true; @@ -180,12 +173,12 @@ Cesium3DTilesetVisualizer.prototype.getBoundingSphere = function ( } const primitive = tilesetData.tilesetPrimitive; - if (!defined(primitive) || !primitive.show) { - return BoundingSphereState.FAILED; + if (!defined(primitive)) { + return BoundingSphereState.PENDING; } - if (!primitive.ready) { - return BoundingSphereState.PENDING; + if (!primitive.show) { + return BoundingSphereState.FAILED; } BoundingSphere.clone(primitive.boundingSphere, result); @@ -235,15 +228,33 @@ Cesium3DTilesetVisualizer.prototype._onCollectionChanged = function ( function removeTileset(visualizer, entity, tilesetHash, primitives) { const tilesetData = tilesetHash[entity.id]; if (defined(tilesetData)) { - primitives.removeAndDestroy(tilesetData.tilesetPrimitive); + if (defined(tilesetData.tilesetPrimitive)) { + primitives.removeAndDestroy(tilesetData.tilesetPrimitive); + } delete tilesetHash[entity.id]; } } -function checkLoad(primitive, entity, tilesetHash) { - primitive.readyPromise.catch(function (error) { +async function createTileset(resource, tilesetHash, entity, primitives) { + tilesetHash[entity.id] = { + url: resource.url, + loadFail: false, + }; + + try { + const tileset = await Cesium3DTileset.fromUrl(resource); + tileset.id = entity; + primitives.add(tileset); + + if (!defined(tilesetHash[entity.id])) { + return; + } + + tilesetHash[entity.id].tilesetPrimitive = tileset; + } catch (error) { console.error(error); tilesetHash[entity.id].loadFail = true; - }); + } } + export default Cesium3DTilesetVisualizer; diff --git a/packages/engine/Source/Scene/Cesium3DTileStyleEngine.js b/packages/engine/Source/Scene/Cesium3DTileStyleEngine.js index 91aa8a313646..bd6eac5a1136 100644 --- a/packages/engine/Source/Scene/Cesium3DTileStyleEngine.js +++ b/packages/engine/Source/Scene/Cesium3DTileStyleEngine.js @@ -33,7 +33,7 @@ Cesium3DTileStyleEngine.prototype.resetDirty = function () { }; Cesium3DTileStyleEngine.prototype.applyStyle = function (tileset) { - if (!tileset.ready) { + if (!defined(tileset.root)) { return; } diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index d5c7674a03ae..3c885a72c8ea 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -10,10 +10,10 @@ import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; -import DeveloperError from "../Core/DeveloperError.js"; import Ellipsoid from "../Core/Ellipsoid.js"; import Event from "../Core/Event.js"; import ImageBasedLighting from "./ImageBasedLighting.js"; +import IonResource from "../Core/IonResource.js"; import JulianDate from "../Core/JulianDate.js"; import ManagedArray from "../Core/ManagedArray.js"; import CesiumMath from "../Core/Math.js"; @@ -53,113 +53,131 @@ import TileBoundingRegion from "./TileBoundingRegion.js"; import TileBoundingSphere from "./TileBoundingSphere.js"; import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; +/** + * @typedef {Object} Cesium3DTileset.ConstructorOptions + * + * Initialization options for the Cesium3DTileset constructor + * + * @property {boolean} [options.show=true] Determines if the tileset will be shown. + * @property {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the tileset's root tile. + * @property {Axis} [options.modelUpAxis=Axis.Y] Which axis is considered up when loading models for tile contents. + * @property {Axis} [options.modelForwardAxis=Axis.X] Which axis is considered forward when loading models for tile contents. + * @property {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the tileset casts or receives shadows from light sources. + * @property {number} [options.maximumScreenSpaceError=16] The maximum screen space error used to drive level of detail refinement. + * @property {number} [options.maximumMemoryUsage=512] The maximum amount of memory in MB that can be used by the tileset. + * @property {boolean} [options.cullWithChildrenBounds=true] Optimization option. Whether to cull tiles using the union of their children bounding volumes. + * @property {boolean} [options.cullRequestsWhileMoving=true] Optimization option. Don't request tiles that will likely be unused when they come back because of the camera's movement. This optimization only applies to stationary tilesets. + * @property {number} [options.cullRequestsWhileMovingMultiplier=60.0] Optimization option. Multiplier used in culling requests while moving. Larger is more aggressive culling, smaller less aggressive culling. + * @property {boolean} [options.preloadWhenHidden=false] Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. + * @property {boolean} [options.preloadFlightDestinations=true] Optimization option. Preload tiles at the camera's flight destination while the camera is in flight. + * @property {boolean} [options.preferLeaves=false] Optimization option. Prefer loading of leaves first. + * @property {boolean} [options.dynamicScreenSpaceError=false] Optimization option. Reduce the screen space error for tiles that are further away from the camera. + * @property {number} [options.dynamicScreenSpaceErrorDensity=0.00278] Density used to adjust the dynamic screen space error, similar to fog density. + * @property {number} [options.dynamicScreenSpaceErrorFactor=4.0] A factor used to increase the computed dynamic screen space error. + * @property {number} [options.dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height at which the density starts to falloff. + * @property {number} [options.progressiveResolutionHeightFraction=0.3] Optimization option. If between (0.0, 0.5], tiles at or above the screen space error for the reduced screen resolution of progressiveResolutionHeightFraction*screenHeight will be prioritized first. This can help get a quick layer of tiles down while full resolution tiles continue to load. + * @property {boolean} [options.foveatedScreenSpaceError=true] Optimization option. Prioritize loading tiles in the center of the screen by temporarily raising the screen space error for tiles around the edge of the screen. Screen space error returns to normal once all the tiles in the center of the screen as determined by the {@link Cesium3DTileset#foveatedConeSize} are loaded. + * @property {number} [options.foveatedConeSize=0.1] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the cone size that determines which tiles are deferred. Tiles that are inside this cone are loaded immediately. Tiles outside the cone are potentially deferred based on how far outside the cone they are and their screen space error. This is controlled by {@link Cesium3DTileset#foveatedInterpolationCallback} and {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation}. Setting this to 0.0 means the cone will be the line formed by the camera position and its view direction. Setting this to 1.0 means the cone encompasses the entire field of view of the camera, disabling the effect. + * @property {number} [options.foveatedMinimumScreenSpaceErrorRelaxation=0.0] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the starting screen space error relaxation for tiles outside the foveated cone. The screen space error will be raised starting with tileset value up to {@link Cesium3DTileset#maximumScreenSpaceError} based on the provided {@link Cesium3DTileset#foveatedInterpolationCallback}. + * @property {Cesium3DTileset.foveatedInterpolationCallback} [options.foveatedInterpolationCallback=Math.lerp] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how much to raise the screen space error for tiles outside the foveated cone, interpolating between {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation} and {@link Cesium3DTileset#maximumScreenSpaceError} + * @property {number} [options.foveatedTimeDelay=0.2] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how long in seconds to wait after the camera stops moving before deferred tiles start loading in. This time delay prevents requesting tiles around the edges of the screen when the camera is moving. Setting this to 0.0 will immediately request all tiles in any given view. + * @property {boolean} [options.skipLevelOfDetail=false] Optimization option. Determines if level of detail skipping should be applied during the traversal. + * @property {number} [options.baseScreenSpaceError=1024] When skipLevelOfDetail is true, the screen space error that must be reached before skipping levels of detail. + * @property {number} [options.skipScreenSpaceErrorFactor=16] When skipLevelOfDetail is true, a multiplier defining the minimum screen space error to skip. Used in conjunction with skipLevels to determine which tiles to load. + * @property {number} [options.skipLevels=1] When skipLevelOfDetail is true, a constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. Used in conjunction with skipScreenSpaceErrorFactor to determine which tiles to load. + * @property {boolean} [options.immediatelyLoadDesiredLevelOfDetail=false] When skipLevelOfDetail is true, only tiles that meet the maximum screen space error will ever be downloaded. Skipping factors are ignored and just the desired tiles are loaded. + * @property {boolean} [options.loadSiblings=false] When skipLevelOfDetail is true, determines whether siblings of visible tiles are always downloaded during traversal. + * @property {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset. + * @property {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this tileset. See {@link Cesium3DTileset#classificationType} for details about restrictions and limitations. + * @property {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid determining the size and shape of the globe. + * @property {object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. + * @property {Cartesian3} [options.lightColor] The light color when shading models. When undefined the scene's light color is used instead. + * @property {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting for this tileset. + * @property {boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled. + * @property {boolean} [options.enableShowOutline=true] Whether to enable outlines for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. This can be set to false to avoid the additional processing of geometry at load time. When false, the showOutlines and outlineColor options are ignored. + * @property {boolean} [options.showOutline=true] Whether to display the outline for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. When true, outlines are displayed. When false, outlines are not displayed. + * @property {Color} [options.outlineColor=Color.BLACK] The color to use when rendering outlines. + * @property {boolean} [options.vectorClassificationOnly=false] Indicates that only the tileset's vector tiles should be used for classification. + * @property {boolean} [options.vectorKeepDecodedPositions=false] Whether vector tiles should keep decoded positions in memory. This is used with {@link Cesium3DTileFeature.getPolylinePositions}. + * @property {string|number} [options.featureIdLabel="featureId_0"] Label of the feature ID set to use for picking and styling. For EXT_mesh_features, this is the feature ID's label property, or "featureId_N" (where N is the index in the featureIds array) when not specified. EXT_feature_metadata did not have a label field, so such feature ID sets are always labeled "featureId_N" where N is the index in the list of all feature Ids, where feature ID attributes are listed before feature ID textures. If featureIdLabel is an integer N, it is converted to the string "featureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. + * @property {string|number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. + * @property {boolean} [options.showCreditsOnScreen=false] Whether to display the credits of this tileset on screen. + * @property {SplitDirection} [options.splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this tileset. + * @property {boolean} [options.projectTo2D=false] Whether to accurately project the tileset to 2D. If this is true, the tileset will be projected accurately to 2D, but it will use more memory to do so. If this is false, the tileset will use less memory and will still render in 2D / CV mode, but its projected positions may be inaccurate. This cannot be set after the tileset has loaded. + * @property {string} [options.debugHeatmapTilePropertyName] The tile variable to colorize as a heatmap. All rendered tiles will be colorized relative to each other's specified variable value. + * @property {boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. + * @property {boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. + * @property {boolean} [options.enableDebugWireframe] For debugging only. This must be true for debugWireframe to work in WebGL1. This cannot be set after the tileset has loaded. + * @property {boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. + * @property {boolean} [options.debugShowBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile. + * @property {boolean} [options.debugShowContentBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile's content. + * @property {boolean} [options.debugShowViewerRequestVolume=false] For debugging only. When true, renders the viewer request volume for each tile. + * @property {boolean} [options.debugShowGeometricError=false] For debugging only. When true, draws labels to indicate the geometric error of each tile. + * @property {boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. + * @property {boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. + * @property {boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. + */ + +/** + * @typedef {Cesium3DTileset.ConstructorOptions} Cesium3DTileset.DeprecatedConstructorOptions + * @property {Resource|string|Promise|Promise} options.url The url to a tileset JSON file. Deprecated. + */ + /** * A {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles tileset}, * used for streaming massive heterogeneous 3D geospatial datasets. * + *
    + * This object is normally not instantiated directly, use {@link Cesium3DTileset.fromUrl}. + *
    + * * @alias Cesium3DTileset * @constructor * - * @param {object} options Object with the following properties: - * @param {Resource|string|Promise|Promise} options.url The url to a tileset JSON file. - * @param {boolean} [options.show=true] Determines if the tileset will be shown. - * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the tileset's root tile. - * @param {Axis} [options.modelUpAxis=Axis.Y] Which axis is considered up when loading models for tile contents. - * @param {Axis} [options.modelForwardAxis=Axis.X] Which axis is considered forward when loading models for tile contents. - * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the tileset casts or receives shadows from light sources. - * @param {number} [options.maximumScreenSpaceError=16] The maximum screen space error used to drive level of detail refinement. - * @param {number} [options.maximumMemoryUsage=512] The maximum amount of memory in MB that can be used by the tileset. - * @param {boolean} [options.cullWithChildrenBounds=true] Optimization option. Whether to cull tiles using the union of their children bounding volumes. - * @param {boolean} [options.cullRequestsWhileMoving=true] Optimization option. Don't request tiles that will likely be unused when they come back because of the camera's movement. This optimization only applies to stationary tilesets. - * @param {number} [options.cullRequestsWhileMovingMultiplier=60.0] Optimization option. Multiplier used in culling requests while moving. Larger is more aggressive culling, smaller less aggressive culling. - * @param {boolean} [options.preloadWhenHidden=false] Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. - * @param {boolean} [options.preloadFlightDestinations=true] Optimization option. Preload tiles at the camera's flight destination while the camera is in flight. - * @param {boolean} [options.preferLeaves=false] Optimization option. Prefer loading of leaves first. - * @param {boolean} [options.dynamicScreenSpaceError=false] Optimization option. Reduce the screen space error for tiles that are further away from the camera. - * @param {number} [options.dynamicScreenSpaceErrorDensity=0.00278] Density used to adjust the dynamic screen space error, similar to fog density. - * @param {number} [options.dynamicScreenSpaceErrorFactor=4.0] A factor used to increase the computed dynamic screen space error. - * @param {number} [options.dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height at which the density starts to falloff. - * @param {number} [options.progressiveResolutionHeightFraction=0.3] Optimization option. If between (0.0, 0.5], tiles at or above the screen space error for the reduced screen resolution of progressiveResolutionHeightFraction*screenHeight will be prioritized first. This can help get a quick layer of tiles down while full resolution tiles continue to load. - * @param {boolean} [options.foveatedScreenSpaceError=true] Optimization option. Prioritize loading tiles in the center of the screen by temporarily raising the screen space error for tiles around the edge of the screen. Screen space error returns to normal once all the tiles in the center of the screen as determined by the {@link Cesium3DTileset#foveatedConeSize} are loaded. - * @param {number} [options.foveatedConeSize=0.1] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the cone size that determines which tiles are deferred. Tiles that are inside this cone are loaded immediately. Tiles outside the cone are potentially deferred based on how far outside the cone they are and their screen space error. This is controlled by {@link Cesium3DTileset#foveatedInterpolationCallback} and {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation}. Setting this to 0.0 means the cone will be the line formed by the camera position and its view direction. Setting this to 1.0 means the cone encompasses the entire field of view of the camera, disabling the effect. - * @param {number} [options.foveatedMinimumScreenSpaceErrorRelaxation=0.0] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the starting screen space error relaxation for tiles outside the foveated cone. The screen space error will be raised starting with tileset value up to {@link Cesium3DTileset#maximumScreenSpaceError} based on the provided {@link Cesium3DTileset#foveatedInterpolationCallback}. - * @param {Cesium3DTileset.foveatedInterpolationCallback} [options.foveatedInterpolationCallback=Math.lerp] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how much to raise the screen space error for tiles outside the foveated cone, interpolating between {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation} and {@link Cesium3DTileset#maximumScreenSpaceError} - * @param {number} [options.foveatedTimeDelay=0.2] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how long in seconds to wait after the camera stops moving before deferred tiles start loading in. This time delay prevents requesting tiles around the edges of the screen when the camera is moving. Setting this to 0.0 will immediately request all tiles in any given view. - * @param {boolean} [options.skipLevelOfDetail=false] Optimization option. Determines if level of detail skipping should be applied during the traversal. - * @param {number} [options.baseScreenSpaceError=1024] When skipLevelOfDetail is true, the screen space error that must be reached before skipping levels of detail. - * @param {number} [options.skipScreenSpaceErrorFactor=16] When skipLevelOfDetail is true, a multiplier defining the minimum screen space error to skip. Used in conjunction with skipLevels to determine which tiles to load. - * @param {number} [options.skipLevels=1] When skipLevelOfDetail is true, a constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. Used in conjunction with skipScreenSpaceErrorFactor to determine which tiles to load. - * @param {boolean} [options.immediatelyLoadDesiredLevelOfDetail=false] When skipLevelOfDetail is true, only tiles that meet the maximum screen space error will ever be downloaded. Skipping factors are ignored and just the desired tiles are loaded. - * @param {boolean} [options.loadSiblings=false] When skipLevelOfDetail is true, determines whether siblings of visible tiles are always downloaded during traversal. - * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset. - * @param {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this tileset. See {@link Cesium3DTileset#classificationType} for details about restrictions and limitations. - * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid determining the size and shape of the globe. - * @param {object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. - * @param {Cartesian3} [options.lightColor] The light color when shading models. When undefined the scene's light color is used instead. - * @param {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting for this tileset. - * @param {boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled. - * @param {boolean} [options.enableShowOutline=true] Whether to enable outlines for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. This can be set to false to avoid the additional processing of geometry at load time. When false, the showOutlines and outlineColor options are ignored. - * @param {boolean} [options.showOutline=true] Whether to display the outline for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. When true, outlines are displayed. When false, outlines are not displayed. - * @param {Color} [options.outlineColor=Color.BLACK] The color to use when rendering outlines. - * @param {boolean} [options.vectorClassificationOnly=false] Indicates that only the tileset's vector tiles should be used for classification. - * @param {boolean} [options.vectorKeepDecodedPositions=false] Whether vector tiles should keep decoded positions in memory. This is used with {@link Cesium3DTileFeature.getPolylinePositions}. - * @param {string|number} [options.featureIdLabel="featureId_0"] Label of the feature ID set to use for picking and styling. For EXT_mesh_features, this is the feature ID's label property, or "featureId_N" (where N is the index in the featureIds array) when not specified. EXT_feature_metadata did not have a label field, so such feature ID sets are always labeled "featureId_N" where N is the index in the list of all feature Ids, where feature ID attributes are listed before feature ID textures. If featureIdLabel is an integer N, it is converted to the string "featureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. - * @param {string|number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. - * @param {boolean} [options.showCreditsOnScreen=false] Whether to display the credits of this tileset on screen. - * @param {SplitDirection} [options.splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this tileset. - * @param {boolean} [options.projectTo2D=false] Whether to accurately project the tileset to 2D. If this is true, the tileset will be projected accurately to 2D, but it will use more memory to do so. If this is false, the tileset will use less memory and will still render in 2D / CV mode, but its projected positions may be inaccurate. This cannot be set after the tileset has loaded. - * @param {string} [options.debugHeatmapTilePropertyName] The tile variable to colorize as a heatmap. All rendered tiles will be colorized relative to each other's specified variable value. - * @param {boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. - * @param {boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. - * @param {boolean} [options.enableDebugWireframe] For debugging only. This must be true for debugWireframe to work in WebGL1. This cannot be set after the tileset has loaded. - * @param {boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. - * @param {boolean} [options.debugShowBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile. - * @param {boolean} [options.debugShowContentBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile's content. - * @param {boolean} [options.debugShowViewerRequestVolume=false] For debugging only. When true, renders the viewer request volume for each tile. - * @param {boolean} [options.debugShowGeometricError=false] For debugging only. When true, draws labels to indicate the geometric error of each tile. - * @param {boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. - * @param {boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. - * @param {boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. + * @param {Cesium3DTileset.DeprecatedConstructorOptions} options An object describing initialization options * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * * @example - * const tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ - * url : 'http://localhost:8002/tilesets/Seattle/tileset.json' - * })); + * try { + * const tileset = await Cesium.Cesium3DTileset.fromUrl( + * "http://localhost:8002/tilesets/Seattle/tileset.json" + * }); + * scene.primitives.add(tileset); + * } catch (error) { + * console.error(`Error creating tileset: ${error}`); + * } * * @example * // Common setting for the skipLevelOfDetail optimization - * const tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ - * url : 'http://localhost:8002/tilesets/Seattle/tileset.json', - * skipLevelOfDetail : true, - * baseScreenSpaceError : 1024, - * skipScreenSpaceErrorFactor : 16, - * skipLevels : 1, - * immediatelyLoadDesiredLevelOfDetail : false, - * loadSiblings : false, - * cullWithChildrenBounds : true - * })); + * const tileset = await Cesium.Cesium3DTileset.fromUrl( + * "http://localhost:8002/tilesets/Seattle/tileset.json", { + * skipLevelOfDetail: true, + * baseScreenSpaceError: 1024, + * skipScreenSpaceErrorFactor: 16, + * skipLevels: 1, + * immediatelyLoadDesiredLevelOfDetail: false, + * loadSiblings: false, + * cullWithChildrenBounds: true + * }); + * scene.primitives.add(tileset); * * @example * // Common settings for the dynamicScreenSpaceError optimization - * const tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ - * url : 'http://localhost:8002/tilesets/Seattle/tileset.json', - * dynamicScreenSpaceError : true, - * dynamicScreenSpaceErrorDensity : 0.00278, - * dynamicScreenSpaceErrorFactor : 4.0, - * dynamicScreenSpaceErrorHeightFalloff : 0.25 - * })); + * const tileset = await Cesium.Cesium3DTileset.fromUrl( + * "http://localhost:8002/tilesets/Seattle/tileset.json", { + * dynamicScreenSpaceError: true, + * dynamicScreenSpaceErrorDensity: 0.00278, + * dynamicScreenSpaceErrorFactor: 4.0, + * dynamicScreenSpaceErrorHeightFalloff: 0.25 + * }); + * scene.primitives.add(tileset); * * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles specification} */ function Cesium3DTileset(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - //>>includeStart('debug', pragmas.debug); - Check.defined("options.url", options.url); - //>>includeEnd('debug'); - this._url = undefined; this._basePath = undefined; this._root = undefined; @@ -974,114 +992,123 @@ function Cesium3DTileset(options) { } this._instanceFeatureIdLabel = instanceFeatureIdLabel; - this._schemaLoader = undefined; - - const that = this; - let resource; - this._readyPromise = Promise.resolve(options.url) - .then(function (url) { - let basePath; - resource = Resource.createIfNeeded(url); - that._resource = resource; - - // ion resources have a credits property we can use for additional attribution. - that._credits = resource.credits; - - if (resource.extension === "json") { - basePath = resource.getBaseUri(true); - } else if (resource.isDataUri) { - basePath = ""; - } + if (defined(options.url)) { + deprecationWarning( + "Cesium3DTileset options.url", + "Cesium3DTileset constructor parameter options.url was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use Cesium3DTileset.fromUrl instead." + ); + const that = this; + let resource; + this._readyPromise = Promise.resolve(options.url) + .then(function (url) { + let basePath; + resource = Resource.createIfNeeded(url); + that._resource = resource; + + // ion resources have a credits property we can use for additional attribution. + that._credits = resource.credits; + + if (resource.extension === "json") { + basePath = resource.getBaseUri(true); + } else if (resource.isDataUri) { + basePath = ""; + } - that._url = resource.url; - that._basePath = basePath; + that._url = resource.url; + that._basePath = basePath; - return Cesium3DTileset.loadJson(resource); - }) - .then(function (tilesetJson) { - if (that.isDestroyed()) { - return; - } - - // This needs to be called before loadTileset() so tile metadata - // can be initialized synchronously in the Cesium3DTile constructor - return processMetadataExtension(that, tilesetJson); - }) - .then(function (tilesetJson) { - if (that.isDestroyed()) { - return; - } + return Cesium3DTileset.loadJson(resource); + }) + .then(function (tilesetJson) { + if (that.isDestroyed()) { + return; + } - // Set these before loading the tileset since _geometricError - // and _scaledGeometricError get accessed during tile creation - that._geometricError = tilesetJson.geometricError; - that._scaledGeometricError = tilesetJson.geometricError; - - that._root = that.loadTileset(resource, tilesetJson); - - // Handle legacy gltfUpAxis option - const gltfUpAxis = defined(tilesetJson.asset.gltfUpAxis) - ? Axis.fromName(tilesetJson.asset.gltfUpAxis) - : Axis.Y; - const modelUpAxis = defaultValue(options.modelUpAxis, gltfUpAxis); - const modelForwardAxis = defaultValue(options.modelForwardAxis, Axis.X); - - const asset = tilesetJson.asset; - that._asset = asset; - that._properties = tilesetJson.properties; - that._extensionsUsed = tilesetJson.extensionsUsed; - that._extensions = tilesetJson.extensions; - that._modelUpAxis = modelUpAxis; - that._modelForwardAxis = modelForwardAxis; - that._extras = tilesetJson.extras; - - const extras = asset.extras; - if ( - defined(extras) && - defined(extras.cesium) && - defined(extras.cesium.credits) - ) { - const extraCredits = extras.cesium.credits; - let credits = that._credits; - if (!defined(credits)) { - credits = []; - that._credits = credits; + // This needs to be called before loadTileset() so tile metadata + // can be initialized synchronously in the Cesium3DTile constructor + return processMetadataExtension(resource, tilesetJson).then( + (metadata) => { + that._metadataExtension = metadata; + return tilesetJson; + } + ); + }) + .then(function (tilesetJson) { + if (that.isDestroyed()) { + return; } - for (let i = 0; i < extraCredits.length; ++i) { - const credit = extraCredits[i]; - credits.push(new Credit(credit.html, that._showCreditsOnScreen)); + + // Set these before loading the tileset since _geometricError + // and _scaledGeometricError get accessed during tile creation + that._geometricError = tilesetJson.geometricError; + that._scaledGeometricError = tilesetJson.geometricError; + + that._root = that.loadTileset(resource, tilesetJson); + + // Handle legacy gltfUpAxis option + const gltfUpAxis = defined(tilesetJson.asset.gltfUpAxis) + ? Axis.fromName(tilesetJson.asset.gltfUpAxis) + : Axis.Y; + const modelUpAxis = defaultValue(options.modelUpAxis, gltfUpAxis); + const modelForwardAxis = defaultValue(options.modelForwardAxis, Axis.X); + + const asset = tilesetJson.asset; + that._asset = asset; + that._properties = tilesetJson.properties; + that._extensionsUsed = tilesetJson.extensionsUsed; + that._extensions = tilesetJson.extensions; + that._modelUpAxis = modelUpAxis; + that._modelForwardAxis = modelForwardAxis; + that._extras = tilesetJson.extras; + + const extras = asset.extras; + if ( + defined(extras) && + defined(extras.cesium) && + defined(extras.cesium.credits) + ) { + const extraCredits = extras.cesium.credits; + let credits = that._credits; + if (!defined(credits)) { + credits = []; + that._credits = credits; + } + for (let i = 0; i < extraCredits.length; ++i) { + const credit = extraCredits[i]; + credits.push(new Credit(credit.html, that._showCreditsOnScreen)); + } } - } - // Save the original, untransformed bounding volume position so we can apply - // the tile transform and model matrix at run time - const boundingVolume = that._root.createBoundingVolume( - tilesetJson.root.boundingVolume, - Matrix4.IDENTITY - ); - const clippingPlanesOrigin = boundingVolume.boundingSphere.center; - // If this origin is above the surface of the earth - // we want to apply an ENU orientation as our best guess of orientation. - // Otherwise, we assume it gets its position/orientation completely from the - // root tile transform and the tileset's model matrix - const originCartographic = that._ellipsoid.cartesianToCartographic( - clippingPlanesOrigin - ); - if ( - defined(originCartographic) && - originCartographic.height > - ApproximateTerrainHeights._defaultMinTerrainHeight - ) { - that._initialClippingPlanesOriginMatrix = Transforms.eastNorthUpToFixedFrame( + // Save the original, untransformed bounding volume position so we can apply + // the tile transform and model matrix at run time + const boundingVolume = that._root.createBoundingVolume( + tilesetJson.root.boundingVolume, + Matrix4.IDENTITY + ); + const clippingPlanesOrigin = boundingVolume.boundingSphere.center; + // If this origin is above the surface of the earth + // we want to apply an ENU orientation as our best guess of orientation. + // Otherwise, we assume it gets its position/orientation completely from the + // root tile transform and the tileset's model matrix + const originCartographic = that._ellipsoid.cartesianToCartographic( clippingPlanesOrigin ); - } - that._clippingPlanesOriginMatrix = Matrix4.clone( - that._initialClippingPlanesOriginMatrix - ); + if ( + defined(originCartographic) && + originCartographic.height > + ApproximateTerrainHeights._defaultMinTerrainHeight + ) { + that._initialClippingPlanesOriginMatrix = Transforms.eastNorthUpToFixedFrame( + clippingPlanesOrigin + ); + } + that._clippingPlanesOriginMatrix = Matrix4.clone( + that._initialClippingPlanesOriginMatrix + ); - return that; - }); + return that; + }); + } } Object.defineProperties(Cesium3DTileset.prototype, { @@ -1108,19 +1135,9 @@ Object.defineProperties(Cesium3DTileset.prototype, { * * @type {object} * @readonly - * - * @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true. */ asset: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this.ready) { - throw new DeveloperError( - "The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true." - ); - } - //>>includeEnd('debug'); - return this._asset; }, }, @@ -1132,19 +1149,9 @@ Object.defineProperties(Cesium3DTileset.prototype, { * * @type {object} * @readonly - * - * @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true. */ extensions: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this.ready) { - throw new DeveloperError( - "The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true." - ); - } - //>>includeEnd('debug'); - return this._extensions; }, }, @@ -1177,8 +1184,6 @@ Object.defineProperties(Cesium3DTileset.prototype, { * @type {object} * @readonly * - * @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true. - * * @example * console.log(`Maximum building height: ${tileset.properties.height.maximum}`); * console.log(`Minimum building height: ${tileset.properties.height.minimum}`); @@ -1188,21 +1193,12 @@ Object.defineProperties(Cesium3DTileset.prototype, { */ properties: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this.ready) { - throw new DeveloperError( - "The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true." - ); - } - //>>includeEnd('debug'); - return this._properties; }, }, /** * When true, the tileset's root tile is loaded and the tileset is ready to render. - * This is set to true right before {@link Cesium3DTileset#readyPromise} is resolved. * * @memberof Cesium3DTileset.prototype * @@ -1213,6 +1209,10 @@ Object.defineProperties(Cesium3DTileset.prototype, { */ ready: { get: function () { + deprecationWarning( + "Cesium3DTileset.ready", + "Cesium3DTileset.ready was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use Cesium3DTileset.fromUrl instead." + ); return defined(this._root); }, }, @@ -1227,6 +1227,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { * * @type {Promise} * @readonly + * @deprecated * * @example * tileset.readyPromise.then(function(tileset) { @@ -1241,6 +1242,10 @@ Object.defineProperties(Cesium3DTileset.prototype, { */ readyPromise: { get: function () { + deprecationWarning( + "Cesium3DTileset.readyPromise", + "Cesium3DTileset.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use Cesium3DTileset.fromUrl instead." + ); return this._readyPromise; }, }, @@ -1537,19 +1542,9 @@ Object.defineProperties(Cesium3DTileset.prototype, { * * @type {Cesium3DTile} * @readonly - * - * @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true. */ root: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this.ready) { - throw new DeveloperError( - "The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true." - ); - } - //>>includeEnd('debug'); - return this._root; }, }, @@ -1562,28 +1557,16 @@ Object.defineProperties(Cesium3DTileset.prototype, { * @type {BoundingSphere} * @readonly * - * @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true. - * * @example - * const tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ - * url : 'http://localhost:8002/tilesets/Seattle/tileset.json' - * })); + * const tileset = await Cesium.Cesium3DTileset.fromUrl("http://localhost:8002/tilesets/Seattle/tileset.json"); * - * tileset.readyPromise.then(function(tileset) { - * // Set the camera to view the newly added tileset - * viewer.camera.viewBoundingSphere(tileset.boundingSphere, new Cesium.HeadingPitchRange(0, -0.5, 0)); - * }); + * viewer.scene.primitives.add(tileset); + * + * // Set the camera to view the newly added tileset + * viewer.camera.viewBoundingSphere(tileset.boundingSphere, new Cesium.HeadingPitchRange(0, -0.5, 0)); */ boundingSphere: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this.ready) { - throw new DeveloperError( - "The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true." - ); - } - //>>includeEnd('debug'); - this._root.updateTransform(this._modelMatrix); return this._root.boundingSphere; }, @@ -1806,8 +1789,6 @@ Object.defineProperties(Cesium3DTileset.prototype, { * * @memberof Cesium3DTileset.prototype * - * @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true. - * * @type {*} * @readonly * @@ -1815,14 +1796,6 @@ Object.defineProperties(Cesium3DTileset.prototype, { */ extras: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this.ready) { - throw new DeveloperError( - "The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true." - ); - } - //>>includeEnd('debug'); - return this._extras; }, }, @@ -1978,6 +1951,174 @@ Object.defineProperties(Cesium3DTileset.prototype, { }, }); +/** + * Creates a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles tileset}, + * used for streaming massive heterogeneous 3D geospatial datasets, from a Cesium ion asset ID. + * + * @param {number} assetId The Cesium ion asset id. + * @param {Cesium3DTileset.ConstructorOptions} options An object describing initialization options + * + * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. + * + * @example + * //Load a Cesium3DTileset with asset ID of 124624234 + * try { + * const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(124624234); + * scene.primitives.add(tileset); + * } catch (error) { + * console.error(`Error creating tileset: ${error}`); + * } + */ +Cesium3DTileset.fromIonAssetId = async function (url, options) { + const resource = await IonResource.fromAssetId(124624234); + return Cesium3DTileset.fromUrl(resource); +}; + +/** + * Creates a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles tileset}, + * used for streaming massive heterogeneous 3D geospatial datasets. + * + * @param {Resource|string} url The url to a tileset JSON file. + * @param {Cesium3DTileset.ConstructorOptions} options An object describing initialization options + * + * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. + * + * @example + * try { + * const tileset = await Cesium.Cesium3DTileset.fromUrl( + * "http://localhost:8002/tilesets/Seattle/tileset.json" + * }); + * scene.primitives.add(tileset); + * } catch (error) { + * console.error(`Error creating tileset: ${error}`); + * } + * + * @example + * // Common setting for the skipLevelOfDetail optimization + * const tileset = await Cesium.Cesium3DTileset.fromUrl( + * "http://localhost:8002/tilesets/Seattle/tileset.json", { + * skipLevelOfDetail: true, + * baseScreenSpaceError: 1024, + * skipScreenSpaceErrorFactor: 16, + * skipLevels: 1, + * immediatelyLoadDesiredLevelOfDetail: false, + * loadSiblings: false, + * cullWithChildrenBounds: true + * }); + * scene.primitives.add(tileset); + * + * @example + * // Common settings for the dynamicScreenSpaceError optimization + * const tileset = await Cesium.Cesium3DTileset.fromUrl( + * "http://localhost:8002/tilesets/Seattle/tileset.json", { + * dynamicScreenSpaceError: true, + * dynamicScreenSpaceErrorDensity: 0.00278, + * dynamicScreenSpaceErrorFactor: 4.0, + * dynamicScreenSpaceErrorHeightFalloff: 0.25 + * }); + * scene.primitives.add(tileset); + */ +Cesium3DTileset.fromUrl = async function (url, options) { + //>>includeStart('debug', pragmas.debug); + Check.defined("url", url); + //>>includeEnd('debug'); + + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + const resource = Resource.createIfNeeded(url); + let basePath; + if (resource.extension === "json") { + basePath = resource.getBaseUri(true); + } else if (resource.isDataUri) { + basePath = ""; + } + + const tilesetJson = await Cesium3DTileset.loadJson(resource); + const metadataExtension = await processMetadataExtension( + resource, + tilesetJson + ); + + const tileset = new Cesium3DTileset(options); + tileset._resource = resource; + tileset._url = resource.url; + tileset._basePath = basePath; + tileset._metadataExtension = metadataExtension; + // Set these before loading the tileset since _geometricError + // and _scaledGeometricError get accessed during tile creation + tileset._geometricError = tilesetJson.geometricError; + tileset._scaledGeometricError = tilesetJson.geometricError; + + const asset = tilesetJson.asset; + tileset._asset = asset; + tileset._extras = tilesetJson.extras; + + let credits = resource.credits; + if (!defined(credits)) { + credits = []; + } + + const assetExtras = asset.extras; + if ( + defined(assetExtras) && + defined(assetExtras.cesium) && + defined(assetExtras.cesium.credits) + ) { + const extraCredits = assetExtras.cesium.credits; + for (let i = 0; i < extraCredits.length; ++i) { + const credit = extraCredits[i]; + credits.push(new Credit(credit.html, tileset._showCreditsOnScreen)); + } + } + tileset._credits = credits; + + // Handle legacy gltfUpAxis option + const gltfUpAxis = defined(tilesetJson.asset.gltfUpAxis) + ? Axis.fromName(tilesetJson.asset.gltfUpAxis) + : Axis.Y; + const modelUpAxis = defaultValue(options.modelUpAxis, gltfUpAxis); + const modelForwardAxis = defaultValue(options.modelForwardAxis, Axis.X); + + tileset._properties = tilesetJson.properties; + tileset._extensionsUsed = tilesetJson.extensionsUsed; + tileset._extensions = tilesetJson.extensions; + tileset._modelUpAxis = modelUpAxis; + tileset._modelForwardAxis = modelForwardAxis; + + tileset._root = tileset.loadTileset(resource, tilesetJson); + + // Save the original, untransformed bounding volume position so we can apply + // the tile transform and model matrix at run time + const boundingVolume = tileset._root.createBoundingVolume( + tilesetJson.root.boundingVolume, + Matrix4.IDENTITY + ); + const clippingPlanesOrigin = boundingVolume.boundingSphere.center; + // If this origin is above the surface of the earth + // we want to apply an ENU orientation as our best guess of orientation. + // Otherwise, we assume it gets its position/orientation completely from the + // root tile transform and the tileset's model matrix + const originCartographic = tileset._ellipsoid.cartesianToCartographic( + clippingPlanesOrigin + ); + if ( + defined(originCartographic) && + originCartographic.height > + ApproximateTerrainHeights._defaultMinTerrainHeight + ) { + tileset._initialClippingPlanesOriginMatrix = Transforms.eastNorthUpToFixedFrame( + clippingPlanesOrigin + ); + } + tileset._clippingPlanesOriginMatrix = Matrix4.clone( + tileset._initialClippingPlanesOriginMatrix + ); + + tileset._readyPromise = Promise.resolve(tileset); + tileset._ready = true; + return tileset; +}; + /** * Provides a hook to override the method used to request the tileset json * useful when fetching tilesets from remote servers @@ -2146,17 +2287,17 @@ function makeTile(tileset, baseResource, tileHeader, parentTile) { * * @param {Cesium3DTileset} tileset The tileset * @param {object} tilesetJson The tileset JSON - * @return {Promise} A promise that resolves to tilesetJson for chaining. + * @return {Promise} The loaded Cesium3DTilesetMetadata * @private */ -function processMetadataExtension(tileset, tilesetJson) { +async function processMetadataExtension(resource, tilesetJson) { const metadataJson = hasExtension(tilesetJson, "3DTILES_metadata") ? tilesetJson.extensions["3DTILES_metadata"] : tilesetJson; let schemaLoader; if (defined(metadataJson.schemaUri)) { - const resource = tileset._resource.getDerivedResource({ + resource = resource.getDerivedResource({ url: metadataJson.schemaUri, }); schemaLoader = ResourceCache.getSchemaLoader({ @@ -2167,19 +2308,19 @@ function processMetadataExtension(tileset, tilesetJson) { schema: metadataJson.schema, }); } else { - return Promise.resolve(tilesetJson); + return; } - tileset._schemaLoader = schemaLoader; - - return schemaLoader.load().then(function (schemaLoader) { - tileset._metadataExtension = new Cesium3DTilesetMetadata({ - schema: schemaLoader.schema, - metadataJson: metadataJson, - }); + await schemaLoader.load(); - return tilesetJson; + const metadataExtension = new Cesium3DTilesetMetadata({ + schema: schemaLoader.schema, + metadataJson: metadataJson, }); + + ResourceCache.unload(schemaLoader); + + return metadataExtension; } const scratchPositionNormal = new Cartesian3(); @@ -2340,7 +2481,7 @@ function sortRequestByPriority(a, b) { * @param {FrameState} frameState */ Cesium3DTileset.prototype.postPassesUpdate = function (frameState) { - if (!this.ready) { + if (!defined(this._root)) { return; } @@ -2363,7 +2504,7 @@ Cesium3DTileset.prototype.postPassesUpdate = function (frameState) { * @param {FrameState} frameState */ Cesium3DTileset.prototype.prePassesUpdate = function (frameState) { - if (!this.ready) { + if (!defined(this._root)) { return; } @@ -2927,7 +3068,7 @@ function raiseLoadProgressEvent(tileset, frameState) { // Events are raised (added to the afterRender queue) here since promises // may resolve outside of the update loop that then raise events, e.g., - // model's readyPromise. + // model's readyEvent if (progressChanged && tileset._tilesLoaded) { frameState.afterRender.push(function () { tileset.allTilesLoaded.raiseEvent(); @@ -2998,7 +3139,7 @@ function update(tileset, frameState, passStatistics, passOptions) { return false; } - if (!tileset.ready) { + if (!defined(tileset._root)) { return false; } @@ -3169,10 +3310,6 @@ Cesium3DTileset.prototype.destroy = function () { this._tileDebugLabels && this._tileDebugLabels.destroy(); this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy(); - if (defined(this._schemaLoader)) { - ResourceCache.unload(this._schemaLoader); - } - // Traverse the tree and destroy all tiles if (defined(this._root)) { const stack = scratchStack; diff --git a/packages/engine/Source/Scene/I3SLayer.js b/packages/engine/Source/Scene/I3SLayer.js index a894236b8bf5..214e2d3fb903 100644 --- a/packages/engine/Source/Scene/I3SLayer.js +++ b/packages/engine/Source/Scene/I3SLayer.js @@ -1,6 +1,7 @@ import defined from "../Core/defined.js"; import Rectangle from "../Core/Rectangle.js"; import Resource from "../Core/Resource.js"; +import RuntimeError from "../Core/RuntimeError.js"; import Cesium3DTileset from "./Cesium3DTileset.js"; import I3SNode from "./I3SNode.js"; @@ -84,7 +85,7 @@ Object.defineProperties(I3SLayer.prototype, { /** * Gets the Cesium3DTileset for this layer. * @memberof I3SLayer.prototype - * @type {Cesium3DTileset} + * @type {Cesium3DTileset|undefined} * @readonly */ tileset: { @@ -167,28 +168,22 @@ Object.defineProperties(I3SLayer.prototype, { * @returns {Promise} A promise that is resolved when the layer data is loaded * @private */ -I3SLayer.prototype.load = function () { - const that = this; - +I3SLayer.prototype.load = async function () { if (this._data.spatialReference.wkid !== 4326) { - console.log( + throw new RuntimeError( `Unsupported spatial reference: ${this._data.spatialReference.wkid}` ); - return Promise.reject(); } - return this._dataProvider._geoidDataIsReadyPromise.then(function () { - return that._loadRootNode().then(function () { - that._create3DTileset(); - return that._tileset.readyPromise.then(function () { - that._rootNode._tile = that._tileset._root; - that._tileset._root._i3sNode = that._rootNode; - if (that.legacyVersion16) { - return that._rootNode._loadChildren(); - } - }); - }); - }); + await this._dataProvider._geoidDataIsReadyPromise; + await this._loadRootNode(); + await this._create3DTileset(); + + this._rootNode._tile = this._tileset._root; + this._tileset._root._i3sNode = this._rootNode; + if (this.legacyVersion16) { + return this._rootNode._loadChildren(); + } }; /** @@ -378,7 +373,7 @@ I3SLayer.prototype._computeExtent = function () { /** * @private */ -I3SLayer.prototype._create3DTileset = function () { +I3SLayer.prototype._create3DTileset = async function () { const inPlaceTileset = { asset: { version: "1.0", @@ -391,7 +386,7 @@ I3SLayer.prototype._create3DTileset = function () { type: "application/json", }); - const inPlaceTilesetURL = URL.createObjectURL(tilesetBlob); + const tilesetUrl = URL.createObjectURL(tilesetBlob); const tilesetOptions = {}; if (defined(this._dataProvider._cesium3dTilesetOptions)) { @@ -401,26 +396,20 @@ I3SLayer.prototype._create3DTileset = function () { } } } - tilesetOptions.url = inPlaceTilesetURL; - tilesetOptions.show = this._dataProvider.show; - - this._tileset = new Cesium3DTileset(tilesetOptions); + this._tileset = await Cesium3DTileset.fromUrl(tilesetUrl, tilesetOptions); + this._tileset.show = this._dataProvider.show; this._tileset._isI3STileSet = true; + this._tileset.tileUnload.addEventListener(function (tile) { + tile._i3sNode._clearGeometryData(); + URL.revokeObjectURL(tile._contentResource._url); + tile._contentResource = tile._i3sNode.resource; + }); - const that = this; - this._tileset.readyPromise.then(function () { - that._tileset.tileUnload.addEventListener(function (tile) { - tile._i3sNode._clearGeometryData(); - URL.revokeObjectURL(tile._contentResource._url); - tile._contentResource = tile._i3sNode.resource; - }); - - that._tileset.tileVisible.addEventListener(function (tile) { - if (defined(tile._i3sNode)) { - tile._i3sNode._loadChildren(); - } - }); + this._tileset.tileVisible.addEventListener(function (tile) { + if (defined(tile._i3sNode)) { + tile._i3sNode._loadChildren(); + } }); }; diff --git a/packages/engine/Source/Scene/createOsmBuildings.js b/packages/engine/Source/Scene/createOsmBuildings.js index d17e717ba374..5e7f8286ab6b 100644 --- a/packages/engine/Source/Scene/createOsmBuildings.js +++ b/packages/engine/Source/Scene/createOsmBuildings.js @@ -2,6 +2,7 @@ import Color from "../Core/Color.js"; import combine from "../Core/combine.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import IonResource from "../Core/IonResource.js"; import Cesium3DTileset from "./Cesium3DTileset.js"; import Cesium3DTileStyle from "./Cesium3DTileStyle.js"; @@ -49,6 +50,11 @@ import Cesium3DTileStyle from "./Cesium3DTileStyle.js"; * })); */ function createOsmBuildings(options) { + deprecationWarning( + "createOsmBuildings", + "createOsmBuildings was deprecated in CesiumJS 1.104. It will be in CesiumJS 1.107. Use createOsmBuildingsAsync instead." + ); + options = combine(options, { url: IonResource.fromAssetId(96188), }); diff --git a/packages/engine/Source/Scene/createOsmBuildingsAsync.js b/packages/engine/Source/Scene/createOsmBuildingsAsync.js new file mode 100644 index 000000000000..8206ce5a1782 --- /dev/null +++ b/packages/engine/Source/Scene/createOsmBuildingsAsync.js @@ -0,0 +1,84 @@ +import Color from "../Core/Color.js"; +import defaultValue from "../Core/defaultValue.js"; +import defined from "../Core/defined.js"; +import IonResource from "../Core/IonResource.js"; +import Cesium3DTileset from "./Cesium3DTileset.js"; +import Cesium3DTileStyle from "./Cesium3DTileStyle.js"; + +/** + * Creates a {@link Cesium3DTileset} instance for the + * {@link https://cesium.com/content/cesium-osm-buildings/|Cesium OSM Buildings} + * tileset. + * + * @function + * + * @param {object} [options] Construction options. Any options allowed by the {@link Cesium3DTileset} constructor + * may be specified here. In addition to those, the following properties are supported: + * @param {Color} [options.defaultColor=Color.WHITE] The default color to use for buildings + * that do not have a color. This parameter is ignored if options.style is specified. + * @param {Cesium3DTileStyle} [options.style] The style to use with the tileset. If not + * specified, a default style is used which gives each building or building part a + * color inferred from its OpenStreetMap tags. If no color can be inferred, + * options.defaultColor is used. + * @param {boolean} [options.enableShowOutline=true] If true, enable rendering outlines. This can be set to false to avoid the additional processing of geometry at load time. + * @param {boolean} [options.showOutline=true] Whether to show outlines around buildings. When true, + * outlines are displayed. When false, outlines are not displayed. + * @returns {Promise} + * + * @see Ion + * + * @example + * // Create Cesium OSM Buildings with default styling + * const viewer = new Cesium.Viewer("cesiumContainer"); + * try { + * const tileset = await Cesium.createOsmBuildingsAsync(); + * viewer.scene.primitives.add(tileset)); + * } catch (error) { + * console.log(`Error creating tileset: ${error}`); + * } + * + * @example + * // Create Cesium OSM Buildings with a custom style highlighting + * // schools and hospitals. + * const viewer = new Cesium.Viewer("cesiumContainer"); + * try { + * const tileset = await Cesium.createOsmBuildingsAsync({ + * style: new Cesium.Cesium3DTileStyle({ + * color: { + * conditions: [ + * ["${feature['building']} === 'hospital'", "color('#0000FF')"], + * ["${feature['building']} === 'school'", "color('#00FF00')"], + * [true, "color('#ffffff')"] + * ] + * } + * }) + * }); + * viewer.scene.primitives.add(tileset)); + * } catch (error) { + * console.log(`Error creating tileset: ${error}`); + * } + */ +async function createOsmBuildingsAsync(options) { + const tileset = await Cesium3DTileset.fromUrl( + IonResource.fromAssetId(96188), + options + ); + + let style = options.style; + + if (!defined(style)) { + const color = defaultValue( + options.defaultColor, + Color.WHITE + ).toCssColorString(); + style = new Cesium3DTileStyle({ + color: `Boolean(\${feature['cesium#color']}) ? color(\${feature['cesium#color']}) : ${color}`, + }); + } + + tileset.style = style; + + return tileset; +} + +export default createOsmBuildingsAsync; diff --git a/packages/engine/Specs/DataSources/Cesium3DTilesetVisualizerSpec.js b/packages/engine/Specs/DataSources/Cesium3DTilesetVisualizerSpec.js index 5181dad6a3af..054adceae035 100644 --- a/packages/engine/Specs/DataSources/Cesium3DTilesetVisualizerSpec.js +++ b/packages/engine/Specs/DataSources/Cesium3DTilesetVisualizerSpec.js @@ -6,6 +6,7 @@ import { Matrix4, Resource, BoundingSphereState, + Cesium3DTileset, ConstantPositionProperty, ConstantProperty, EntityCollection, @@ -35,22 +36,10 @@ describe( scene.destroyForSpecs(); }); - function allPrimitivesReady() { - const promises = []; - for (let i = 0; i < scene.primitives.length; ++i) { - promises.push(scene.primitives.get(i).readyPromise); - } - return Promise.all(promises).catch(function (e) { - // 404 errors - }); - } - afterEach(function () { - return allPrimitivesReady().then(function () { - if (defined(visualizer)) { - visualizer = visualizer.destroy(); - } - }); + if (defined(visualizer)) { + visualizer = visualizer.destroy(); + } }); it("constructor throws if no scene is passed.", function () { @@ -85,7 +74,7 @@ describe( visualizer = undefined; }); - it("object with no model does not create one.", function () { + it("object with no tileset does not create one.", function () { const entityCollection = new EntityCollection(); visualizer = new Cesium3DTilesetVisualizer(scene, entityCollection); @@ -97,7 +86,7 @@ describe( expect(scene.primitives.length).toEqual(0); }); - it("object with no position does not set modelMatrix.", function () { + it("object with no position does not set modelMatrix.", async function () { const entityCollection = new EntityCollection(); visualizer = new Cesium3DTilesetVisualizer(scene, entityCollection); @@ -107,11 +96,15 @@ describe( visualizer.update(JulianDate.now()); + await pollToPromise(function () { + return defined(scene.primitives.get(0)); + }); + const tilesetPrimitive = scene.primitives.get(0); expect(tilesetPrimitive.modelMatrix).toEqual(Matrix4.IDENTITY); }); - it("A Cesium3DTilesetGraphics causes a primitive to be created and updated.", function () { + it("A Cesium3DTilesetGraphics causes a primitive to be created and updated.", async function () { const time = JulianDate.now(); const entityCollection = new EntityCollection(); visualizer = new Cesium3DTilesetVisualizer(scene, entityCollection); @@ -129,7 +122,12 @@ describe( visualizer.update(time); + await pollToPromise(function () { + return defined(scene.primitives.get(0)); + }); + expect(scene.primitives.length).toEqual(1); + expect(scene.primitives.get(0)).toBeInstanceOf(Cesium3DTileset); const primitive = scene.primitives.get(0); visualizer.update(time); @@ -137,7 +135,7 @@ describe( expect(primitive.maximumScreenSpaceError).toEqual(24.0); }); - it("A Cesium3DTilesetGraphics with a Resource causes a primitive to be created.", function () { + it("A Cesium3DTilesetGraphics with a Resource causes a primitive to be created.", async function () { const time = JulianDate.now(); const entityCollection = new EntityCollection(); visualizer = new Cesium3DTilesetVisualizer(scene, entityCollection); @@ -158,10 +156,15 @@ describe( visualizer.update(time); + await pollToPromise(function () { + return defined(scene.primitives.get(0)); + }); + expect(scene.primitives.length).toEqual(1); + expect(scene.primitives.get(0)).toBeInstanceOf(Cesium3DTileset); }); - it("removing removes primitives.", function () { + it("removing removes primitives.", async function () { const entityCollection = new EntityCollection(); visualizer = new Cesium3DTilesetVisualizer(scene, entityCollection); @@ -175,16 +178,17 @@ describe( ); testObject.tileset = tileset; visualizer.update(time); - return allPrimitivesReady().then(function () { - expect(scene.primitives.length).toEqual(1); - visualizer.update(time); - entityCollection.removeAll(); - visualizer.update(time); - expect(scene.primitives.length).toEqual(0); + await pollToPromise(function () { + return defined(scene.primitives.get(0)); }); + expect(scene.primitives.length).toEqual(1); + visualizer.update(time); + entityCollection.removeAll(); + visualizer.update(time); + expect(scene.primitives.length).toEqual(0); }); - it("Visualizer sets id property.", function () { + it("Visualizer sets id property.", async function () { const entityCollection = new EntityCollection(); visualizer = new Cesium3DTilesetVisualizer(scene, entityCollection); @@ -199,11 +203,15 @@ describe( tileset.uri = new ConstantProperty(tilesetUrl); visualizer.update(time); + await pollToPromise(function () { + return defined(scene.primitives.get(0)); + }); + const tilesetPrimitive = scene.primitives.get(0); expect(tilesetPrimitive.id).toEqual(testObject); }); - it("Computes bounding sphere.", function () { + it("Computes bounding sphere.", async function () { const entityCollection = new EntityCollection(); visualizer = new Cesium3DTilesetVisualizer(scene, entityCollection); @@ -218,19 +226,18 @@ describe( tileset.uri = new ConstantProperty(tilesetUrl); visualizer.update(time); - const tilesetPrimitive = scene.primitives.get(0); const result = new BoundingSphere(); let state = visualizer.getBoundingSphere(testObject, result); expect(state).toBe(BoundingSphereState.PENDING); - return pollToPromise(function () { - scene.render(); + await pollToPromise(function () { state = visualizer.getBoundingSphere(testObject, result); return state !== BoundingSphereState.PENDING; - }).then(function () { - expect(state).toBe(BoundingSphereState.DONE); - expect(result).toEqual(tilesetPrimitive.boundingSphere); }); + + const tilesetPrimitive = scene.primitives.get(0); + expect(state).toBe(BoundingSphereState.DONE); + expect(result).toEqual(tilesetPrimitive.boundingSphere); }); it("Fails bounding sphere for entity without tileset.", function () { @@ -243,7 +250,7 @@ describe( expect(state).toBe(BoundingSphereState.FAILED); }); - it("Fails bounding sphere when model fails to load.", function () { + it("Fails bounding sphere when tileset fails to load.", async function () { const entityCollection = new EntityCollection(); visualizer = new Cesium3DTilesetVisualizer(scene, entityCollection); @@ -261,14 +268,14 @@ describe( const result = new BoundingSphere(); let state = visualizer.getBoundingSphere(testObject, result); expect(state).toBe(BoundingSphereState.PENDING); - return allPrimitivesReady() - .catch(function (e) { - // 404 error - }) - .finally(function () { - state = visualizer.getBoundingSphere(testObject, result); - expect(state).toBe(BoundingSphereState.FAILED); - }); + + await pollToPromise(function () { + state = visualizer.getBoundingSphere(testObject, result); + return state !== BoundingSphereState.PENDING; + }); + + state = visualizer.getBoundingSphere(testObject, result); + expect(state).toBe(BoundingSphereState.FAILED); }); it("Compute bounding sphere throws without entity.", function () { diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js index 10bef3ed990d..92902d24dcff 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js @@ -39,6 +39,7 @@ import { RequestScheduler, Resource, ResourceCache, + RuntimeError, Transforms, } from "../../index.js"; import Cesium3DTilesTester from "../../../../Specs/Cesium3DTilesTester.js"; @@ -263,12 +264,6 @@ describe( return tileset._selectedTiles.indexOf(tile) > -1; } - it("throws with undefined url", function () { - expect(function () { - return new Cesium3DTileset(); - }).toThrowDeveloperError(); - }); - it("rejects readyPromise with invalid tileset JSON file", function () { spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( url, @@ -372,7 +367,7 @@ describe( it("rejects readyPromise with invalid tileset version", function () { const tilesetJson = { asset: { - version: 2.0, + version: "2.0", }, }; @@ -392,7 +387,7 @@ describe( it("rejects readyPromise with unsupported extension", function () { const tilesetJson = { asset: { - version: 1.0, + version: "1.0", }, extensionsUsed: ["unsupported_extension"], extensionsRequired: ["unsupported_extension"], @@ -411,27 +406,68 @@ describe( }); }); - it("url and tilesetUrl set up correctly given tileset JSON filepath", function () { - const path = "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; - const tileset = new Cesium3DTileset({ - url: path, - }); - return Promise.resolve(tileset).then(function () { - expect(tileset.resource.url).toEqual(path); - return tileset.readyPromise; + it("fromUrl throws without url", async function () { + await expectAsync( + Cesium3DTileset.fromUrl() + ).toBeRejectedWithDeveloperError( + "url is required, actual value was undefined" + ); + }); + + it("fromUrl throws with unsupported version", async function () { + const tilesetJson = { + asset: { + version: "2.0", + }, + }; + + const uri = `data:text/plain;base64,${btoa(JSON.stringify(tilesetJson))}`; + await expectAsync(Cesium3DTileset.fromUrl(uri)).toBeRejectedWithError( + RuntimeError, + "The tileset must be 3D Tiles version 0.0, 1.0, or 1.1" + ); + }); + + it("fromUrl throws with unsupported extension", async function () { + const tilesetJson = { + asset: { + version: "1.0", + }, + extensionsUsed: ["unsupported_extension"], + extensionsRequired: ["unsupported_extension"], + }; + + const uri = `data:text/plain;base64,${btoa(JSON.stringify(tilesetJson))}`; + await expectAsync(Cesium3DTileset.fromUrl(uri)).toBeRejectedWithError( + RuntimeError, + "Unsupported 3D Tiles Extension: unsupported_extension" + ); + }); + + it("fromUrl throws with invalid tileset JSON file", async function () { + await expectAsync(Cesium3DTileset.fromUrl("invalid.json")).toBeRejected(); + }); + + it("fromUrl resolves with file resource", async function () { + const resource = new Resource({ + url: "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json", }); + + const tileset = await Cesium3DTileset.fromUrl(resource); + expect(tileset).toBeInstanceOf(Cesium3DTileset); }); - it("url and tilesetUrl set up correctly given path with query string", function () { + it("url and tilesetUrl set up correctly given tileset JSON filepath", async function () { + const path = "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; + const tileset = await Cesium3DTileset.fromUrl(path); + expect(tileset.resource.url).toEqual(path); + }); + + it("url and tilesetUrl set up correctly given path with query string", async function () { const path = "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; const param = "?param1=1¶m2=2"; - const tileset = new Cesium3DTileset({ - url: path + param, - }); - return Promise.resolve(tileset).then(function () { - expect(tileset.resource.url).toEqual(path + param); - return tileset.readyPromise; - }); + const tileset = await Cesium3DTileset.fromUrl(path + param); + expect(tileset.resource.url).toEqual(path + param); }); it("resolves readyPromise", function () { @@ -487,15 +523,9 @@ describe( }); }); - it("gets root tile", function () { - options.url = tilesetUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); - expect(function () { - return tileset.root; - }).toThrowDeveloperError(); - return tileset.readyPromise.then(function () { - expect(tileset.root).toBeDefined(); - }); + it("gets root tile", async function () { + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); + expect(tileset.root).toBeDefined(); }); it("hasExtension returns true if the tileset JSON file uses the specified extension", function () { @@ -551,53 +581,12 @@ describe( }); }); - it("throws when getting asset and tileset is not ready", function () { - const tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); - expect(function () { - return tileset.asset; - }).toThrowDeveloperError(); - return tileset.readyPromise; - }); - - it("throws when getting extensions and tileset is not ready", function () { - const tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); - expect(function () { - return tileset.extensions; - }).toThrowDeveloperError(); - return tileset.readyPromise; - }); - - it("throws when getting properties and tileset is not ready", function () { - const tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); - expect(function () { - return tileset.properties; - }).toThrowDeveloperError(); - return tileset.readyPromise; - }); - - it("throws when getting extras and tileset is not ready", function () { - const tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); - expect(function () { - return tileset.extras; - }).toThrowDeveloperError(); - return tileset.readyPromise; - }); - it("requests tile with invalid magic", async function () { const invalidMagicBuffer = Cesium3DTilesTester.generateBatchedTileBuffer({ magic: [120, 120, 120, 120], }); - options.url = tilesetUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); - await tileset.readyPromise; + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); + scene.primitives.add(tileset); const failedSpy = jasmine.createSpy("listenerSpy"); tileset.tileFailed.addEventListener(failedSpy); @@ -624,9 +613,8 @@ describe( it("handles failed tile requests", async function () { viewRootOnly(); - options.url = tilesetUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); - await tileset.readyPromise; + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); + scene.primitives.add(tileset); const failedSpy = jasmine.createSpy("listenerSpy"); tileset.tileFailed.addEventListener(failedSpy); @@ -657,9 +645,8 @@ describe( it("handles failed tile processing", async function () { viewRootOnly(); - options.url = tilesetUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); - await tileset.readyPromise; + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); + scene.primitives.add(tileset); const failedSpy = jasmine.createSpy("listenerSpy"); tileset.tileFailed.addEventListener(failedSpy); @@ -867,34 +854,23 @@ describe( }); }); - it("verify statistics", function () { - options.url = tilesetUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + it("verify statistics", async function () { + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); - // Verify initial values + // Verify initial values after root and children are requested const statistics = tileset._statistics; expect(statistics.visited).toEqual(0); expect(statistics.numberOfCommands).toEqual(0); expect(statistics.numberOfPendingRequests).toEqual(0); expect(statistics.numberOfTilesProcessing).toEqual(0); - return Cesium3DTilesTester.waitForReady(scene, tileset).then(function () { - // Check that root and children are requested - expect(statistics.visited).toEqual(5); - expect(statistics.numberOfCommands).toEqual(0); - expect(statistics.numberOfPendingRequests).toEqual(5); - expect(statistics.numberOfTilesProcessing).toEqual(0); - - // Wait for all tiles to load and check that they are all visited and rendered - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( - function () { - expect(statistics.visited).toEqual(5); - expect(statistics.numberOfCommands).toEqual(5); - expect(statistics.numberOfPendingRequests).toEqual(0); - expect(statistics.numberOfTilesProcessing).toEqual(0); - } - ); - }); + scene.primitives.add(tileset); + // Wait for all tiles to load and check that they are all visited and rendered + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + expect(statistics.visited).toEqual(5); + expect(statistics.numberOfCommands).toEqual(5); + expect(statistics.numberOfPendingRequests).toEqual(0); + expect(statistics.numberOfTilesProcessing).toEqual(0); }); function checkPointAndFeatureCounts(tileset, features, points, triangles) { @@ -935,62 +911,74 @@ describe( ); } - it("verify batched features statistics", function () { - options.url = withBatchTableUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + it("verify batched features statistics", async function () { + const tileset = await Cesium3DTileset.fromUrl(withBatchTableUrl, options); + scene.primitives.add(tileset); return checkPointAndFeatureCounts(tileset, 10, 0, 120); }); - it("verify no batch table features statistics", function () { - options.url = noBatchIdsUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + it("verify no batch table features statistics", async function () { + const tileset = await Cesium3DTileset.fromUrl(noBatchIdsUrl, options); + scene.primitives.add(tileset); return checkPointAndFeatureCounts(tileset, 0, 0, 120); }); - it("verify instanced features statistics", function () { - options.url = instancedRedMaterialUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + it("verify instanced features statistics", async function () { + const tileset = await Cesium3DTileset.fromUrl( + instancedRedMaterialUrl, + options + ); + scene.primitives.add(tileset); return checkPointAndFeatureCounts(tileset, 25, 0, 12); }); - it("verify composite features statistics", function () { - options.url = compositeUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + it("verify composite features statistics", async function () { + const tileset = await Cesium3DTileset.fromUrl(compositeUrl, options); + scene.primitives.add(tileset); return checkPointAndFeatureCounts(tileset, 35, 0, 132); }); - it("verify tileset of tilesets features statistics", function () { - options.url = tilesetOfTilesetsUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + it("verify tileset of tilesets features statistics", async function () { + const tileset = await Cesium3DTileset.fromUrl( + tilesetOfTilesetsUrl, + options + ); + scene.primitives.add(tileset); return checkPointAndFeatureCounts(tileset, 50, 0, 600); }); - it("verify points statistics", function () { + it("verify points statistics", async function () { viewPointCloud(); - options.url = pointCloudUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + const tileset = await Cesium3DTileset.fromUrl(pointCloudUrl, options); + scene.primitives.add(tileset); return checkPointAndFeatureCounts(tileset, 0, 1000, 0); }); - it("verify triangle statistics", function () { - options.url = tilesetEmptyRootUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + it("verify triangle statistics", async function () { + const tileset = await Cesium3DTileset.fromUrl( + tilesetEmptyRootUrl, + options + ); + scene.primitives.add(tileset); return checkPointAndFeatureCounts(tileset, 40, 0, 480); }); - it("verify batched points statistics", function () { + it("verify batched points statistics", async function () { viewPointCloud(); - options.url = pointCloudBatchedUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + const tileset = await Cesium3DTileset.fromUrl( + pointCloudBatchedUrl, + options + ); + scene.primitives.add(tileset); return checkPointAndFeatureCounts(tileset, 8, 1000, 0); }); @@ -2443,41 +2431,31 @@ describe( }); }); - it("tilesLoaded", function () { - options.url = tilesetUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + it("tilesLoaded", async function () { + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); + scene.primitives.add(tileset); expect(tileset.tilesLoaded).toBe(false); - return tileset.readyPromise.then(function () { - expect(tileset.tilesLoaded).toBe(false); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( - function () { - expect(tileset.tilesLoaded).toBe(true); - } - ); - }); + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + expect(tileset.tilesLoaded).toBe(true); }); - it("all tiles loaded event is raised", function () { + it("all tiles loaded event is raised", async function () { // Called first when only the root is visible and it becomes loaded, and then again when // the rest of the tileset is visible and all tiles are loaded. const spyUpdate1 = jasmine.createSpy("listener"); const spyUpdate2 = jasmine.createSpy("listener"); viewRootOnly(); - options.url = tilesetUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); tileset.allTilesLoaded.addEventListener(spyUpdate1); tileset.initialTilesLoaded.addEventListener(spyUpdate2); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( - function () { - viewAllTiles(); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( - function () { - expect(spyUpdate1.calls.count()).toEqual(2); - expect(spyUpdate2.calls.count()).toEqual(1); - } - ); - } - ); + scene.primitives.add(tileset); + + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + viewAllTiles(); + + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + expect(spyUpdate1.calls.count()).toEqual(2); + expect(spyUpdate2.calls.count()).toEqual(1); }); it("tile visible event is raised", function () { @@ -2613,9 +2591,8 @@ describe( it("destroys before tile finishes loading", async function () { viewRootOnly(); - options.url = tilesetUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); - await tileset.readyPromise; + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); + scene.primitives.add(tileset); const root = tileset.root; scene.renderForSpecs(); // Request root scene.primitives.remove(tileset); @@ -3724,24 +3701,18 @@ describe( }); }); - it("maximumMemoryUsage throws when negative", function () { - const tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); + it("maximumMemoryUsage throws when negative", async function () { + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); expect(function () { tileset.maximumMemoryUsage = -1; }).toThrowDeveloperError(); - return tileset.readyPromise; }); - it("maximumScreenSpaceError throws when negative", function () { - const tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); + it("maximumScreenSpaceError throws when negative", async function () { + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); expect(function () { tileset.maximumScreenSpaceError = -1; }).toThrowDeveloperError(); - return tileset.readyPromise; }); it("propagates tile transform down the tree", function () { @@ -3960,10 +3931,10 @@ describe( }); }); - it("does not add commands or stencil clear command with no selected tiles", function () { - options.url = tilesetUrl; + it("does not add commands or stencil clear command with no selected tiles", async function () { options.skipLevelOfDetail = true; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); + scene.primitives.add(tileset); scene.renderForSpecs(); const statistics = tileset._statistics; expect(tileset._selectedTiles.length).toEqual(0); @@ -4655,28 +4626,20 @@ describe( ); }); - it("throws if frameState is undefined", function () { - const tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); + it("throws if frameState is undefined", async function () { + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); expect(function () { tileset.updateForPass(); }).toThrowDeveloperError(); - - return tileset.readyPromise; }); - it("throws if tilesetPassState is undefined", function () { - const tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); + it("throws if tilesetPassState is undefined", async function () { + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl, options); expect(function () { tileset.updateForPass(scene.frameState); }).toThrowDeveloperError(); - - return tileset.readyPromise; }); }); @@ -5585,12 +5548,8 @@ describe( viewNothing(); - const tileset = scene.primitives.add( - new Cesium3DTileset({ - url: multipleContentsUrl, - }) - ); - await tileset.readyPromise; + const tileset = await Cesium3DTileset.fromUrl(multipleContentsUrl); + scene.primitives.add(tileset); viewAllTiles(); scene.renderForSpecs(); @@ -5687,9 +5646,12 @@ describe( expect(statistics.numberOfTilesWithContentReady).toBe(1); }); - it("verify multiple content statistics", function () { - options.url = multipleContentsUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + it("verify multiple content statistics", async function () { + const tileset = await Cesium3DTileset.fromUrl( + multipleContentsUrl, + options + ); + scene.primitives.add(tileset); return checkPointAndFeatureCounts(tileset, 35, 0, 132); }); @@ -5763,11 +5725,8 @@ describe( viewNothing(); let errorCount = 0; - const tileset = scene.primitives.add( - new Cesium3DTileset({ - url: multipleContentsUrl, - }) - ); + + const tileset = await Cesium3DTileset.fromUrl(multipleContentsUrl); tileset.tileFailed.addEventListener(function (event) { errorCount++; expect(endsWith(event.url, ".json")).toBe(true); @@ -5775,7 +5734,7 @@ describe( "External tilesets are disallowed inside multiple contents" ); }); - await tileset.readyPromise; + scene.primitives.add(tileset); spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { const externalTileset = { @@ -5985,9 +5944,12 @@ describe( expect(statistics.numberOfTilesWithContentReady).toBe(1); }); - it("verify multiple content statistics (legacy)", function () { - options.url = multipleContentsLegacyUrl; - const tileset = scene.primitives.add(new Cesium3DTileset(options)); + it("verify multiple content statistics (legacy)", async function () { + const tileset = await Cesium3DTileset.fromUrl( + multipleContentsLegacyUrl, + options + ); + scene.primitives.add(tileset); return checkPointAndFeatureCounts(tileset, 35, 0, 132); }); diff --git a/packages/engine/Specs/Scene/I3SLayerSpec.js b/packages/engine/Specs/Scene/I3SLayerSpec.js index cbc8bcd52792..4dbf710091a9 100644 --- a/packages/engine/Specs/Scene/I3SLayerSpec.js +++ b/packages/engine/Specs/Scene/I3SLayerSpec.js @@ -1,4 +1,9 @@ -import { I3SLayer, I3SDataProvider, Math as CesiumMath } from "../../index.js"; +import { + I3SLayer, + I3SDataProvider, + Math as CesiumMath, + RuntimeError, +} from "../../index.js"; describe("Scene/I3SLayer", function () { const rootNodePageEntry = { @@ -284,7 +289,7 @@ describe("Scene/I3SLayer", function () { }); }); - it("creates 3d tileset", function () { + it("creates 3d tileset", async function () { const mockI3SProvider = createMockI3SProvider(); const testLayer = new I3SLayer(mockI3SProvider, layerData); testLayer._nodePages = [ @@ -293,21 +298,15 @@ describe("Scene/I3SLayer", function () { ]; testLayer._nodePageFetches = [Promise.resolve()]; - return testLayer - ._loadRootNode() - .then(function () { - testLayer._create3DTileset(); - expect(testLayer.tileset).toBeDefined(); + await testLayer._loadRootNode(); + await testLayer._create3DTileset(); - return testLayer.tileset.readyPromise; - }) - .then(function () { - expect(testLayer.tileset.tileUnload._listeners.length).toEqual(1); - expect(testLayer.tileset.tileVisible._listeners.length).toEqual(1); - }); + expect(testLayer.tileset).toBeDefined(); + expect(testLayer.tileset.tileUnload._listeners.length).toEqual(1); + expect(testLayer.tileset.tileVisible._listeners.length).toEqual(1); }); - it("creates 3d tileset with options", function () { + it("creates 3d tileset with options", async function () { const cesium3dTilesetOptions = { debugShowBoundingVolume: true, maximumScreenSpaceError: 8, @@ -326,23 +325,17 @@ describe("Scene/I3SLayer", function () { ]; testLayer._nodePageFetches = [Promise.resolve()]; - return testLayer - ._loadRootNode() - .then(function () { - testLayer._create3DTileset(); - expect(testLayer.tileset).toBeDefined(); - expect(testLayer.tileset.debugShowBoundingVolume).toEqual(true); - expect(testLayer.tileset.maximumScreenSpaceError).toEqual(8); + await testLayer._loadRootNode(); + await testLayer._create3DTileset(); + expect(testLayer.tileset).toBeDefined(); + expect(testLayer.tileset.debugShowBoundingVolume).toEqual(true); + expect(testLayer.tileset.maximumScreenSpaceError).toEqual(8); - return testLayer._tileset.readyPromise; - }) - .then(function () { - expect(testLayer.tileset.tileUnload._listeners.length).toEqual(1); - expect(testLayer.tileset.tileVisible._listeners.length).toEqual(1); - }); + expect(testLayer.tileset.tileUnload._listeners.length).toEqual(1); + expect(testLayer.tileset.tileVisible._listeners.length).toEqual(1); }); - it("loads i3s layer", function () { + it("loads i3s layer", async function () { const mockI3SProvider = createMockI3SProvider(); const testLayer = new I3SLayer(mockI3SProvider, layerData); testLayer._nodePages = [ @@ -351,15 +344,14 @@ describe("Scene/I3SLayer", function () { ]; testLayer._nodePageFetches = [Promise.resolve()]; - return testLayer.load().then(function () { - expect(testLayer.tileset).toBeDefined(); - expect(testLayer._rootNode).toBeDefined(); - expect(testLayer._rootNode._tile).toBe(testLayer.tileset._root); - expect(testLayer._rootNode).toBe(testLayer.tileset._root.i3sNode); - }); + await testLayer.load(); + expect(testLayer.tileset).toBeDefined(); + expect(testLayer._rootNode).toBeDefined(); + expect(testLayer._rootNode._tile).toBe(testLayer.tileset._root); + expect(testLayer._rootNode).toBe(testLayer.tileset._root.i3sNode); }); - it("load i3s layer rejects unsupported spatial reference", function () { + it("load i3s layer rejects unsupported spatial reference", async function () { const invalidLayerData = { nodePages: { lodSelectionMetricType: "maxScreenThresholdSQ", @@ -381,19 +373,9 @@ describe("Scene/I3SLayer", function () { ]; testLayer._nodePageFetches = [Promise.resolve()]; - spyOn(console, "log"); - - return testLayer - .load() - .then(function () { - fail( - "Promise should not be resolved for unsupported spatial reference" - ); - }) - .catch(function () { - expect(console.log).toHaveBeenCalledWith( - `Unsupported spatial reference: ${invalidLayerData.spatialReference.wkid}` - ); - }); + await expectAsync(testLayer.load()).toBeRejectedWithError( + RuntimeError, + `Unsupported spatial reference: ${invalidLayerData.spatialReference.wkid}` + ); }); }); diff --git a/packages/engine/Specs/Scene/Vector3DTileContentSpec.js b/packages/engine/Specs/Scene/Vector3DTileContentSpec.js index 6bdf4724dc7f..4106b41aa0ed 100644 --- a/packages/engine/Specs/Scene/Vector3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Vector3DTileContentSpec.js @@ -2219,10 +2219,10 @@ describe( ); }); - it("destroys", function () { - const tileset = new Cesium3DTileset({ - url: vectorTilePolygonsWithBatchTableTileset, - }); + it("destroys", async function () { + const tileset = await Cesium3DTileset.fromUrl( + vectorTilePolygonsWithBatchTableTileset + ); expect(tileset.isDestroyed()).toEqual(false); tileset.destroy(); expect(tileset.isDestroyed()).toEqual(true); diff --git a/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js b/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js index 60c337fc9b7d..8b9f0683dd8b 100644 --- a/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js +++ b/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js @@ -1224,7 +1224,8 @@ Object.defineProperties(Cesium3DTilesInspectorViewModel.prototype, { if (defined(tileset)) { const that = this; - tileset.readyPromise.then(function (t) { + // This is here for backwards compatibility. It can be removed when Cesium3DTileset.readyPromise is fully deprecated. + tileset._readyPromise.then(function (t) { if (!that.isDestroyed()) { that._properties(t.properties); } diff --git a/packages/widgets/Source/Viewer/Viewer.js b/packages/widgets/Source/Viewer/Viewer.js index 751b2d0f5033..42216d4aa28f 100644 --- a/packages/widgets/Source/Viewer/Viewer.js +++ b/packages/widgets/Source/Viewer/Viewer.js @@ -2233,7 +2233,8 @@ function updateZoomTarget(viewer) { // If zoomTarget was Cesium3DTileset if (target instanceof Cesium3DTileset || target instanceof VoxelPrimitive) { - return target.readyPromise + // This is here for backwards compatibility and can be removed once Cesium3DTileset.readyPromise is removed. + return target._readyPromise .then(function () { const boundingSphere = target.boundingSphere; // If offset was originally undefined then give it base value instead of empty object diff --git a/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js b/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js index 94cc1760c526..b38d51013900 100644 --- a/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js +++ b/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js @@ -53,36 +53,31 @@ describe( }); describe("tileset options", function () { - it("show properties", function () { + it("show properties", async function () { viewModel = new Cesium3DTilesInspectorViewModel( scene, performanceContainer ); - const tileset = new Cesium3DTileset({ + const tileset = await Cesium3DTileset.fromUrl({ url: tilesetUrl, }); viewModel.tileset = tileset; - return tileset.readyPromise.then(function () { - expect(viewModel.properties.indexOf("id") !== -1).toBe(true); - expect(viewModel.properties.indexOf("Longitude") !== -1).toBe(true); - expect(viewModel.properties.indexOf("Latitude") !== -1).toBe(true); - expect(viewModel.properties.indexOf("Height") !== -1).toBe(true); - viewModel.destroy(); - }); + expect(viewModel.properties.indexOf("id") !== -1).toBe(true); + expect(viewModel.properties.indexOf("Longitude") !== -1).toBe(true); + expect(viewModel.properties.indexOf("Latitude") !== -1).toBe(true); + expect(viewModel.properties.indexOf("Height") !== -1).toBe(true); + viewModel.destroy(); }); }); describe("display options", function () { - beforeAll(function () { + beforeAll(async function () { viewModel = new Cesium3DTilesInspectorViewModel( scene, performanceContainer ); - const tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl); viewModel.tileset = tileset; - return tileset.readyPromise; }); afterAll(function () { @@ -228,15 +223,12 @@ describe( }); describe("update options", function () { - beforeAll(function () { + beforeAll(async function () { viewModel = new Cesium3DTilesInspectorViewModel( scene, performanceContainer ); - viewModel.tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); - return viewModel.tileset.readyPromise; + viewModel.tileset = await Cesium3DTileset.fromUrl(tilesetUrl); }); afterAll(function () { @@ -270,7 +262,7 @@ describe( describe("style options", function () { let style; - beforeAll(function () { + beforeAll(async function () { style = new Cesium3DTileStyle({ color: { conditions: [ @@ -292,11 +284,7 @@ describe( scene, performanceContainer ); - viewModel.tileset = new Cesium3DTileset({ - url: tilesetUrl, - }); - - return viewModel.tileset.readyPromise; + viewModel.tileset = await Cesium3DTileset.fromUrl(tilesetUrl); }); afterAll(function () { diff --git a/packages/widgets/Specs/Viewer/ViewerSpec.js b/packages/widgets/Specs/Viewer/ViewerSpec.js index b626261028ed..cfabcc15a8b3 100644 --- a/packages/widgets/Specs/Viewer/ViewerSpec.js +++ b/packages/widgets/Specs/Viewer/ViewerSpec.js @@ -1240,95 +1240,67 @@ describe( }).toThrowDeveloperError(); }); - it("zoomTo returns false if Cesium3DTileset fails to load", function () { - viewer = createViewer(container); - const tileset = new Cesium3DTileset({ - url: "foo/bar", - }); - - return tileset.readyPromise - .catch(function (e) { - expect(e.toString()).toEqual("Request has failed. Status Code: 404"); - }) - .then(function () { - return viewer.zoomTo(tileset); - }) - .then((result) => { - expect(result).toBe(false); - }); - }); - - it("zoomTo zooms to Cesium3DTileset with default offset when offset not defined", function () { + it("zoomTo zooms to Cesium3DTileset with default offset when offset not defined", async function () { viewer = createViewer(container); const path = "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; - const tileset = new Cesium3DTileset({ - url: path, - }); + const tileset = await Cesium3DTileset.fromUrl(path); - // load the tileset then check tests - return tileset.readyPromise.then(function () { - const expectedBoundingSphere = tileset.boundingSphere; - const expectedOffset = new HeadingPitchRange( - 0.0, - -0.5, - expectedBoundingSphere.radius - ); + const expectedBoundingSphere = tileset.boundingSphere; + const expectedOffset = new HeadingPitchRange( + 0.0, + -0.5, + expectedBoundingSphere.radius + ); - let wasCompleted = false; - spyOn(viewer.camera, "viewBoundingSphere").and.callFake(function ( - boundingSphere, - offset - ) { - expect(boundingSphere).toEqual(expectedBoundingSphere); - expect(offset).toEqual(expectedOffset); - wasCompleted = true; - }); - const promise = viewer.zoomTo(tileset); + let wasCompleted = false; + spyOn(viewer.camera, "viewBoundingSphere").and.callFake(function ( + boundingSphere, + offset + ) { + expect(boundingSphere).toEqual(expectedBoundingSphere); + expect(offset).toEqual(expectedOffset); + wasCompleted = true; + }); + const promise = viewer.zoomTo(tileset); - viewer._postRender(); + viewer._postRender(); - return promise.then(function () { - expect(wasCompleted).toEqual(true); - }); + return promise.then(function () { + expect(wasCompleted).toEqual(true); }); }); - it("zoomTo zooms to Cesium3DTileset with offset", function () { + it("zoomTo zooms to Cesium3DTileset with offset", async function () { viewer = createViewer(container); const path = "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; - const tileset = new Cesium3DTileset({ - url: path, - }); + const tileset = await Cesium3DTileset.fromUrl(path); - // load the tileset then check tests - return tileset.readyPromise.then(function () { - const expectedBoundingSphere = tileset.boundingSphere; - const expectedOffset = new HeadingPitchRange( - 0.4, - 1.2, - 4.0 * expectedBoundingSphere.radius - ); + const expectedBoundingSphere = tileset.boundingSphere; + const expectedOffset = new HeadingPitchRange( + 0.4, + 1.2, + 4.0 * expectedBoundingSphere.radius + ); - const promise = viewer.zoomTo(tileset, expectedOffset); - let wasCompleted = false; - spyOn(viewer.camera, "viewBoundingSphere").and.callFake(function ( - boundingSphere, - offset - ) { - expect(boundingSphere).toEqual(expectedBoundingSphere); - expect(offset).toEqual(expectedOffset); - wasCompleted = true; - }); + const promise = viewer.zoomTo(tileset, expectedOffset); + let wasCompleted = false; + spyOn(viewer.camera, "viewBoundingSphere").and.callFake(function ( + boundingSphere, + offset + ) { + expect(boundingSphere).toEqual(expectedBoundingSphere); + expect(offset).toEqual(expectedOffset); + wasCompleted = true; + }); - viewer._postRender(); + viewer._postRender(); - return promise.then(function () { - expect(wasCompleted).toEqual(true); - }); + return promise.then(function () { + expect(wasCompleted).toEqual(true); }); }); @@ -1587,110 +1559,95 @@ describe( }).toThrowDeveloperError(); }); - it("flyTo flies to Cesium3DTileset with default offset when options not defined", function () { + it("flyTo flies to Cesium3DTileset with default offset when options not defined", async function () { viewer = createViewer(container); const path = "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; - const tileset = new Cesium3DTileset({ - url: path, - }); + const tileset = await Cesium3DTileset.fromUrl(path); - // load tileset to test - return tileset.readyPromise.then(function () { - const promise = viewer.flyTo(tileset); - let wasCompleted = false; + const promise = viewer.flyTo(tileset); + let wasCompleted = false; - spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(function ( - target, - options - ) { - expect(options.offset).toBeDefined(); - expect(options.duration).toBeUndefined(); - expect(options.maximumHeight).toBeUndefined(); - wasCompleted = true; - options.complete(); - }); + spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(function ( + target, + options + ) { + expect(options.offset).toBeDefined(); + expect(options.duration).toBeUndefined(); + expect(options.maximumHeight).toBeUndefined(); + wasCompleted = true; + options.complete(); + }); - viewer._postRender(); + viewer._postRender(); - return promise.then(function () { - expect(wasCompleted).toEqual(true); - }); + return promise.then(function () { + expect(wasCompleted).toEqual(true); }); }); - it("flyTo flies to Cesium3DTileset with default offset when offset not defined", function () { + it("flyTo flies to Cesium3DTileset with default offset when offset not defined", async function () { viewer = createViewer(container); const path = "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; - const tileset = new Cesium3DTileset({ - url: path, - }); + const tileset = await Cesium3DTileset.fromUrl(path); const options = {}; - // load tileset to test - return tileset.readyPromise.then(function () { - const promise = viewer.flyTo(tileset, options); - let wasCompleted = false; + const promise = viewer.flyTo(tileset, options); + let wasCompleted = false; - spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(function ( - target, - options - ) { - expect(options.offset).toBeDefined(); - expect(options.duration).toBeUndefined(); - expect(options.maximumHeight).toBeUndefined(); - wasCompleted = true; - options.complete(); - }); + spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(function ( + target, + options + ) { + expect(options.offset).toBeDefined(); + expect(options.duration).toBeUndefined(); + expect(options.maximumHeight).toBeUndefined(); + wasCompleted = true; + options.complete(); + }); - viewer._postRender(); + viewer._postRender(); - return promise.then(function () { - expect(wasCompleted).toEqual(true); - }); + return promise.then(function () { + expect(wasCompleted).toEqual(true); }); }); - it("flyTo flies to Cesium3DTileset when options are defined", function () { + it("flyTo flies to Cesium3DTileset when options are defined", async function () { viewer = createViewer(container); const path = "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; - const tileset = new Cesium3DTileset({ - url: path, - }); + const tileset = await Cesium3DTileset.fromUrl(path); - // load tileset to test - return tileset.readyPromise.then(function () { - const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3); - const options = { - offset: offsetVal, - duration: 3.0, - maximumHeight: 5.0, - }; + const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3); + const options = { + offset: offsetVal, + duration: 3.0, + maximumHeight: 5.0, + }; - const promise = viewer.flyTo(tileset, options); - let wasCompleted = false; + const promise = viewer.flyTo(tileset, options); + let wasCompleted = false; - spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(function ( - target, - options - ) { - expect(options.duration).toBeDefined(); - expect(options.maximumHeight).toBeDefined(); - wasCompleted = true; - options.complete(); - }); + spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(function ( + target, + options + ) { + expect(options.duration).toBeDefined(); + expect(options.maximumHeight).toBeDefined(); + wasCompleted = true; + options.complete(); + }); - viewer._postRender(); + viewer._postRender(); - return promise.then(function () { - expect(wasCompleted).toEqual(true); - }); + return promise.then(function () { + expect(wasCompleted).toEqual(true); }); }); From 4f8c8cd19b95f42d152210ef69c89c467d24616b Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Mon, 20 Mar 2023 16:02:37 -0400 Subject: [PATCH 05/18] Update Sandcastles --- Apps/Sandcastle/gallery/3D Tiles BIM.html | 3 +- .../gallery/3D Tiles Clipping Planes.html | 152 +++--- Apps/Sandcastle/gallery/3D Tiles Compare.html | 21 +- .../gallery/3D Tiles Feature Picking.html | 10 +- .../gallery/3D Tiles Feature Styling.html | 2 +- Apps/Sandcastle/gallery/3D Tiles Formats.html | 83 ++-- .../gallery/3D Tiles Inspector.html | 31 +- .../gallery/3D Tiles Interactivity.html | 27 +- .../Sandcastle/gallery/3D Tiles Interior.html | 10 +- .../gallery/3D Tiles Next CDB Yemen.html | 99 ++-- ...es Next Photogrammetry Classification.html | 38 +- .../gallery/3D Tiles Next S2 Globe.html | 15 +- ...D Tiles Photogrammetry Classification.html | 70 +-- .../gallery/3D Tiles Photogrammetry.html | 13 +- .../3D Tiles Point Cloud Classification.html | 55 +-- .../gallery/3D Tiles Point Cloud Shading.html | 126 ++--- .../gallery/3D Tiles Point Cloud Styling.html | 19 +- .../gallery/3D Tiles Point Cloud.html | 14 +- .../3D Tiles Terrain Classification.html | 22 +- .../gallery/Aerometrex San Francisco.html | 20 +- .../Sandcastle/gallery/Ambient Occlusion.html | 20 +- .../Sandcastle/gallery/Clamp to 3D Tiles.html | 21 +- .../gallery/Classification Types.html | 12 +- Apps/Sandcastle/gallery/Classification.html | 10 +- .../gallery/Custom Shaders 3D Tiles.html | 34 +- .../Custom Shaders Property Textures.html | 19 +- Apps/Sandcastle/gallery/FXAA.html | 15 +- Apps/Sandcastle/gallery/Fog Post Process.html | 23 +- Apps/Sandcastle/gallery/MSAA.html | 34 +- .../gallery/Montreal Point Cloud.html | 29 +- .../gallery/Polylines on 3D Tiles.html | 65 ++- .../gallery/Sample Height from 3D Tiles.html | 13 +- .../gallery/Scene Rendering Performance.html | 21 +- .../gallery/Terrain Clipping Planes.html | 84 ++-- .../3D Tiles Performance Testing.html | 3 +- .../gallery/development/3D Tiles Split.html | 79 ++-- .../development/Many Clipping Planes.html | 19 +- .../gallery/development/Pick From Ray.html | 7 +- .../gallery/development/Picking.html | 434 ++++++++---------- .../gallery/development/Shadows.html | 24 +- CHANGES.md | 2 +- Documentation/CustomShaderGuide/README.md | 9 +- Documentation/OfflineGuide/README.md | 13 +- packages/engine/Source/Scene/BufferLoader.js | 2 + .../engine/Source/Scene/Cesium3DTileset.js | 6 +- packages/engine/Source/Scene/GltfLoader.js | 14 + packages/engine/Source/Scene/ResourceCache.js | 2 +- .../Source/Scene/Tileset3DTileContent.js | 4 +- .../Source/Scene/createOsmBuildingsAsync.js | 8 +- 49 files changed, 979 insertions(+), 877 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles BIM.html b/Apps/Sandcastle/gallery/3D Tiles BIM.html index 06df8cba02b3..542878ac3053 100644 --- a/Apps/Sandcastle/gallery/3D Tiles BIM.html +++ b/Apps/Sandcastle/gallery/3D Tiles BIM.html @@ -209,8 +209,7 @@ }); } catch (error) { console.log(`Error loading tileset: ${error}`); - } - //Sandcastle_End + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html index 9547272431fa..b6ce63560bf0 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html +++ b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html @@ -142,7 +142,8 @@ } let tileset; - function loadTileset(url) { + async function loadTileset(resource) { + const currentExampleType = viewModel.currentExampleType; clippingPlanes = new Cesium.ClippingPlaneCollection({ planes: [ new Cesium.ClippingPlane( @@ -153,77 +154,76 @@ edgeWidth: viewModel.edgeStylingEnabled ? 1.0 : 0.0, }); - tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: url, + try { + const url = await Promise.resolve(resource); + tileset = await Cesium.Cesium3DTileset.fromUrl(url, { clippingPlanes: clippingPlanes, - }) - ); - - tileset.debugShowBoundingVolume = - viewModel.debugBoundingVolumesEnabled; - return tileset.readyPromise - .then(function () { - const boundingSphere = tileset.boundingSphere; - const radius = boundingSphere.radius; + }); + if (currentExampleType !== viewModel.currentExampleType) { + // Another tileset was loaded, discard the current result + return; + } - viewer.zoomTo( - tileset, - new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0) + viewer.scene.primitives.add(tileset); + + tileset.debugShowBoundingVolume = + viewModel.debugBoundingVolumesEnabled; + const boundingSphere = tileset.boundingSphere; + const radius = boundingSphere.radius; + + viewer.zoomTo( + tileset, + new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0) + ); + + if ( + !Cesium.Matrix4.equals( + tileset.root.transform, + Cesium.Matrix4.IDENTITY + ) + ) { + // The clipping plane is initially positioned at the tileset's root transform. + // Apply an additional matrix to center the clipping plane on the bounding sphere center. + const transformCenter = Cesium.Matrix4.getTranslation( + tileset.root.transform, + new Cesium.Cartesian3() + ); + const transformCartographic = Cesium.Cartographic.fromCartesian( + transformCenter ); + const boundingSphereCartographic = Cesium.Cartographic.fromCartesian( + tileset.boundingSphere.center + ); + const height = + boundingSphereCartographic.height - + transformCartographic.height; + clippingPlanes.modelMatrix = Cesium.Matrix4.fromTranslation( + new Cesium.Cartesian3(0.0, 0.0, height) + ); + } - if ( - !Cesium.Matrix4.equals( - tileset.root.transform, - Cesium.Matrix4.IDENTITY - ) - ) { - // The clipping plane is initially positioned at the tileset's root transform. - // Apply an additional matrix to center the clipping plane on the bounding sphere center. - const transformCenter = Cesium.Matrix4.getTranslation( - tileset.root.transform, - new Cesium.Cartesian3() - ); - const transformCartographic = Cesium.Cartographic.fromCartesian( - transformCenter - ); - const boundingSphereCartographic = Cesium.Cartographic.fromCartesian( - tileset.boundingSphere.center - ); - const height = - boundingSphereCartographic.height - - transformCartographic.height; - clippingPlanes.modelMatrix = Cesium.Matrix4.fromTranslation( - new Cesium.Cartesian3(0.0, 0.0, height) - ); - } - - for (let i = 0; i < clippingPlanes.length; ++i) { - const plane = clippingPlanes.get(i); - const planeEntity = viewer.entities.add({ - position: boundingSphere.center, - plane: { - dimensions: new Cesium.Cartesian2( - radius * 2.5, - radius * 2.5 - ), - material: Cesium.Color.WHITE.withAlpha(0.1), - plane: new Cesium.CallbackProperty( - createPlaneUpdateFunction(plane), - false - ), - outline: true, - outlineColor: Cesium.Color.WHITE, - }, - }); - - planeEntities.push(planeEntity); - } - return tileset; - }) - .catch(function (error) { - console.log(error); - }); + for (let i = 0; i < clippingPlanes.length; ++i) { + const plane = clippingPlanes.get(i); + const planeEntity = viewer.entities.add({ + position: boundingSphere.center, + plane: { + dimensions: new Cesium.Cartesian2(radius * 2.5, radius * 2.5), + material: Cesium.Color.WHITE.withAlpha(0.1), + plane: new Cesium.CallbackProperty( + createPlaneUpdateFunction(plane), + false + ), + outline: true, + outlineColor: Cesium.Color.WHITE, + }, + }); + + planeEntities.push(planeEntity); + } + return tileset; + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } } function loadModel(url) { @@ -308,11 +308,12 @@ } else if (newValue === clipObjects[1]) { loadTileset(pointCloudUrl); } else if (newValue === clipObjects[2]) { - loadTileset(instancedUrl); - // Position the instanced tileset above terrain - tileset.modelMatrix = new Cesium.Matrix4.fromTranslation( - new Cesium.Cartesian3(15.0, -58.6, 50.825) - ); + loadTileset(instancedUrl).then(() => { + // Position the instanced tileset above terrain + tileset.modelMatrix = new Cesium.Matrix4.fromTranslation( + new Cesium.Cartesian3(15.0, -58.6, 50.825) + ); + }); } else { loadModel(modelUrl); } @@ -336,7 +337,10 @@ function reset() { viewer.entities.removeAll(); - viewer.scene.primitives.remove(tileset); + if (Cesium.defined(tileset)) { + viewer.scene.primitives.remove(tileset); + } + planeEntities = []; targetY = 0.0; tileset = undefined; diff --git a/Apps/Sandcastle/gallery/3D Tiles Compare.html b/Apps/Sandcastle/gallery/3D Tiles Compare.html index 5f494f9abe77..6e209e819f00 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Compare.html +++ b/Apps/Sandcastle/gallery/3D Tiles Compare.html @@ -54,18 +54,19 @@ //Sandcastle_Begin const viewer = new Cesium.Viewer("cesiumContainer"); - const left = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(69380), - }) - ); + try { + const left = await Cesium.Cesium3DTileset.fromIonAssetId(69380); + viewer.scene.primitives.add(left); + left.splitDirection = Cesium.SplitDirection.LEFT; - left.splitDirection = Cesium.SplitDirection.LEFT; + viewer.zoomTo(left); - const right = viewer.scene.primitives.add(Cesium.createOsmBuildings()); - right.splitDirection = Cesium.SplitDirection.RIGHT; - - viewer.zoomTo(left); + const right = await Cesium.createOsmBuildingsAsync(); + viewer.scene.primitives.add(right); + right.splitDirection = Cesium.SplitDirection.RIGHT; + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } // Sync the position of the slider with the split position const slider = document.getElementById("slider"); diff --git a/Apps/Sandcastle/gallery/3D Tiles Feature Picking.html b/Apps/Sandcastle/gallery/3D Tiles Feature Picking.html index 8174e5fdec84..b486801ef7e5 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Feature Picking.html +++ b/Apps/Sandcastle/gallery/3D Tiles Feature Picking.html @@ -59,10 +59,12 @@ }); // Load the NYC buildings tileset - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(75343), - }); - viewer.scene.primitives.add(tileset); + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(75343); + viewer.scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } // HTML overlay for showing feature name on mouseover const nameOverlay = document.createElement("div"); diff --git a/Apps/Sandcastle/gallery/3D Tiles Feature Styling.html b/Apps/Sandcastle/gallery/3D Tiles Feature Styling.html index c9fe5b050ebd..ddadf3af6590 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Feature Styling.html +++ b/Apps/Sandcastle/gallery/3D Tiles Feature Styling.html @@ -61,7 +61,7 @@

    Loading...

    const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); // Add Cesium OSM buildings to the scene as our example 3D Tileset. - const osmBuildingsTileset = Cesium.createOsmBuildings(); + const osmBuildingsTileset = await Cesium.createOsmBuildingsAsync(); viewer.scene.primitives.add(osmBuildingsTileset); // Set the initial camera to look at Seattle diff --git a/Apps/Sandcastle/gallery/3D Tiles Formats.html b/Apps/Sandcastle/gallery/3D Tiles Formats.html index e141b25e538e..7c7d1fb50cf8 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Formats.html +++ b/Apps/Sandcastle/gallery/3D Tiles Formats.html @@ -140,59 +140,62 @@ viewer.shadows = enabled; }); + let resourceToLoad; Cesium.knockout .getObservable(viewModel, "selectedTileset") - .subscribe(function (options) { + .subscribe(async function (options) { if (Cesium.defined(tileset)) { scene.primitives.remove(tileset); } if (!Cesium.defined(options)) { inspectorViewModel.tileset = undefined; + resourceToLoad = undefined; return; } - tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: options.resource, + resourceToLoad = options.resource; + try { + tileset = await Cesium.Cesium3DTileset.fromUrl(resourceToLoad, { enableDebugWireframe: true, - }) - ); + }); + if (options.resource !== resourceToLoad) { + // Another tileset was loaded. Discard the result. + return; + } + viewer.scene.primitives.add(tileset); - tileset.readyPromise - .then(function () { - inspectorViewModel.tileset = tileset; - viewer.zoomTo( - tileset, - new Cesium.HeadingPitchRange( - 0, - -2.0, - Math.max(100.0 - tileset.boundingSphere.radius, 0.0) - ) - ); + inspectorViewModel.tileset = tileset; + viewer.zoomTo( + tileset, + new Cesium.HeadingPitchRange( + 0, + -2.0, + Math.max(100.0 - tileset.boundingSphere.radius, 0.0) + ) + ); - const properties = tileset.properties; - if ( - Cesium.defined(properties) && - Cesium.defined(properties.Height) - ) { - tileset.style = new Cesium.Cesium3DTileStyle({ - color: { - conditions: [ - ["${Height} >= 83", "color('purple', 0.5)"], - ["${Height} >= 80", "color('red')"], - ["${Height} >= 70", "color('orange')"], - ["${Height} >= 12", "color('yellow')"], - ["${Height} >= 7", "color('lime')"], - ["${Height} >= 1", "color('cyan')"], - ["true", "color('blue')"], - ], - }, - }); - } - }) - .catch(function (error) { - throw error; - }); + const properties = tileset.properties; + if ( + Cesium.defined(properties) && + Cesium.defined(properties.Height) + ) { + tileset.style = new Cesium.Cesium3DTileStyle({ + color: { + conditions: [ + ["${Height} >= 83", "color('purple', 0.5)"], + ["${Height} >= 80", "color('red')"], + ["${Height} >= 70", "color('orange')"], + ["${Height} >= 12", "color('yellow')"], + ["${Height} >= 7", "color('lime')"], + ["${Height} >= 1", "color('cyan')"], + ["true", "color('blue')"], + ], + }, + }); + } + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } }); viewModel.selectedTileset = viewModel.tilesets[0]; diff --git a/Apps/Sandcastle/gallery/3D Tiles Inspector.html b/Apps/Sandcastle/gallery/3D Tiles Inspector.html index 736bde4e8f3d..1ad857bd51a8 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Inspector.html +++ b/Apps/Sandcastle/gallery/3D Tiles Inspector.html @@ -46,22 +46,23 @@ viewer.extend(Cesium.viewerCesium3DTilesInspectorMixin); const inspectorViewModel = viewer.cesium3DTilesInspector.viewModel; - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(75343), - enableDebugWireframe: true, - }); - viewer.scene.primitives.add(tileset); - - await tileset.readyPromise; + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(75343, { + enableDebugWireframe: true, + }); + viewer.scene.primitives.add(tileset); - viewer.zoomTo( - tileset, - new Cesium.HeadingPitchRange( - 0.0, - -0.5, - tileset.boundingSphere.radius / 4.0 - ) - ); //Sandcastle_End + viewer.zoomTo( + tileset, + new Cesium.HeadingPitchRange( + 0.0, + -0.5, + tileset.boundingSphere.radius / 4.0 + ) + ); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Apps/Sandcastle/gallery/3D Tiles Interactivity.html b/Apps/Sandcastle/gallery/3D Tiles Interactivity.html index 30426497ff74..a55fae14eb18 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Interactivity.html +++ b/Apps/Sandcastle/gallery/3D Tiles Interactivity.html @@ -114,16 +114,20 @@ endTransform: Cesium.Matrix4.IDENTITY, }); + let style; // Load the NYC buildings tileset. - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(75343), - }); - scene.primitives.add(tileset); - tileset.style = new Cesium.Cesium3DTileStyle({ - meta: { - description: "'Building ${BIN} has height ${Height}.'", - }, - }); + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(75343); + scene.primitives.add(tileset); + style = new Cesium.Cesium3DTileStyle({ + meta: { + description: "'Building ${BIN} has height ${Height}.'", + }, + }); + tileset.style = style; + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas); @@ -185,8 +189,11 @@ } // Evaluate feature description + if (!Cesium.defined(style)) { + return; + } console.log( - `Description : ${tileset.style.meta.description.evaluate(feature)}` + `Description : ${style.meta.description.evaluate(feature)}` ); } diff --git a/Apps/Sandcastle/gallery/3D Tiles Interior.html b/Apps/Sandcastle/gallery/3D Tiles Interior.html index 3fc2975278f8..38c63747e69e 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Interior.html +++ b/Apps/Sandcastle/gallery/3D Tiles Interior.html @@ -39,10 +39,12 @@ // San Miguel model created by Guillermo M. Leal Llaguno. Cleaned up and hosted by Morgan McGuire: http://graphics.cs.williams.edu/data/meshes.xml const viewer = new Cesium.Viewer("cesiumContainer"); - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(125737), - }); - viewer.scene.primitives.add(tileset); + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(125737); + viewer.scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } const initialPosition = new Cesium.Cartesian3( -1111583.3721328347, diff --git a/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html b/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html index 515f30909f29..c69b695c55ba 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html +++ b/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html @@ -52,19 +52,6 @@ const scene = viewer.scene; scene.light.intensity = 7.0; - // 3D Tiles Next converted from CDB of Aden, Yemen (CDB provided by Presagis) - const terrainTileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(667809), - }) - ); - const buildingsTileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(666747), - maximumScreenSpaceError: 12, - }) - ); - const cameraTransforms = { tileset: { destination: new Cesium.Cartesian3( @@ -149,19 +136,6 @@ }); } - buildingsTileset.readyPromise.then(function (tileset) { - const center = Cesium.Cartesian3.fromDegrees( - 45.04192, - 12.753525, - 2000 - ); - const heading = Cesium.Math.toRadians(50.0); - const pitch = Cesium.Math.toRadians(-20.0); - const range = 5000.0; - - flyCameraTo(cameraTransforms.tileset); - }); - // --- Style --- const buildingStyle = new Cesium.Cesium3DTileStyle({ @@ -408,34 +382,61 @@ } } - const modes = [ - { - text: "No style", - onselect: function () { - resetHighlight(); - buildingsTileset.style = undefined; - terrainTileset.style = undefined; + try { + // 3D Tiles Next converted from CDB of Aden, Yemen (CDB provided by Presagis) + const terrainTileset = await Cesium.Cesium3DTileset.fromIonAssetId( + 1240402 + ); + viewer.scene.primitives.add(terrainTileset); + const buildingsTileset = await Cesium.Cesium3DTileset.fromIonAssetId( + 666747, + { + maximumScreenSpaceError: 12, + } + ); + viewer.scene.primitives.add(buildingsTileset); + const center = Cesium.Cartesian3.fromDegrees( + 45.04192, + 12.753525, + 2000 + ); + const heading = Cesium.Math.toRadians(50.0); + const pitch = Cesium.Math.toRadians(-20.0); + const range = 5000.0; + + flyCameraTo(cameraTransforms.tileset); + + const modes = [ + { + text: "No style", + onselect: function () { + resetHighlight(); + buildingsTileset.style = undefined; + terrainTileset.style = undefined; + }, }, - }, - { - text: "Style buildings based on height", - onselect: function () { - resetHighlight(); - buildingsTileset.style = buildingStyle; - terrainTileset.style = undefined; + { + text: "Style buildings based on height", + onselect: function () { + resetHighlight(); + buildingsTileset.style = buildingStyle; + terrainTileset.style = undefined; + }, }, - }, - { - text: "Style terrain based on materials", - onselect: function () { - buildingsTileset.style = undefined; - terrainTileset.style = terrainStyle; + { + text: "Style terrain based on materials", + onselect: function () { + buildingsTileset.style = undefined; + terrainTileset.style = terrainStyle; + }, }, - }, - ]; + ]; + Sandcastle.addToolbarMenu(modes); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } Sandcastle.addToolbarMenu(locations); - Sandcastle.addToolbarMenu(modes); Sandcastle.addToggleButton( "Enable terrain picking", enablePicking, diff --git a/Apps/Sandcastle/gallery/3D Tiles Next Photogrammetry Classification.html b/Apps/Sandcastle/gallery/3D Tiles Next Photogrammetry Classification.html index 97f23ac0aba5..1f024d001d33 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Next Photogrammetry Classification.html +++ b/Apps/Sandcastle/gallery/3D Tiles Next Photogrammetry Classification.html @@ -48,24 +48,6 @@ const scene = viewer.scene; - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(775877), - }); - - const translation = new Cesium.Cartesian3( - -1.398521324920626, - 0.7823052871729486, - 0.7015244410592609 - ); - tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); - - tileset.maximumScreenSpaceError = 8.0; - scene.pickTranslucentDepth = true; - scene.light.intensity = 7.0; - - viewer.scene.primitives.add(tileset); - viewer.zoomTo(tileset); - // Fly to a nice overview of the city. viewer.camera.flyTo({ destination: new Cesium.Cartesian3( @@ -80,6 +62,26 @@ ), }); + let tileset; + try { + tileset = await Cesium.Cesium3DTileset.fromIonAssetId(775877); + + const translation = new Cesium.Cartesian3( + -1.398521324920626, + 0.7823052871729486, + 0.7015244410592609 + ); + tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); + + tileset.maximumScreenSpaceError = 8.0; + scene.pickTranslucentDepth = true; + scene.light.intensity = 7.0; + + viewer.scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } + // Styles ============================================================================= const classificationStyle = new Cesium.Cesium3DTileStyle({ diff --git a/Apps/Sandcastle/gallery/3D Tiles Next S2 Globe.html b/Apps/Sandcastle/gallery/3D Tiles Next S2 Globe.html index f1d18063448a..28f1198ab04b 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Next S2 Globe.html +++ b/Apps/Sandcastle/gallery/3D Tiles Next S2 Globe.html @@ -74,13 +74,16 @@ easingFunction: Cesium.EasingFunction.QUADRATIC_IN_OUT, }); - // MAXAR OWT WFF 1.2 Base Globe - const tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(691510), + let tileset; + try { + // MAXAR OWT WFF 1.2 Base Globe + tileset = await Cesium.Cesium3DTileset.fromIonAssetId(691510, { maximumScreenSpaceError: 4, - }) - ); + }); + scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } // --- Style --- diff --git a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html index f59d6b688333..d2cff77c2412 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html +++ b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry Classification.html @@ -41,41 +41,47 @@ terrain: Cesium.Terrain.fromWorldTerrain(), }); - // A normal b3dm tileset containing photogrammetry - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(40866), - }); - viewer.scene.primitives.add(tileset); - viewer.zoomTo(tileset); + try { + // A normal b3dm tileset containing photogrammetry + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(40866); + viewer.scene.primitives.add(tileset); + viewer.zoomTo(tileset); - const classificationTilesetUrl = - "../../SampleData/Cesium3DTiles/Classification/Photogrammetry/tileset.json"; - // A b3dm tileset used to classify the photogrammetry tileset - const classificationTileset = new Cesium.Cesium3DTileset({ - url: classificationTilesetUrl, - classificationType: Cesium.ClassificationType.CESIUM_3D_TILE, - }); - classificationTileset.style = new Cesium.Cesium3DTileStyle({ - color: "rgba(255, 0, 0, 0.5)", - }); - viewer.scene.primitives.add(classificationTileset); + const classificationTilesetUrl = + "../../SampleData/Cesium3DTiles/Classification/Photogrammetry/tileset.json"; + // A b3dm tileset used to classify the photogrammetry tileset + const classificationTileset = await Cesium.Cesium3DTileset.fromUrl( + classificationTilesetUrl, + { + classificationType: Cesium.ClassificationType.CESIUM_3D_TILE, + } + ); + classificationTileset.style = new Cesium.Cesium3DTileStyle({ + color: "rgba(255, 0, 0, 0.5)", + }); + viewer.scene.primitives.add(classificationTileset); - // The same b3dm tileset used for classification, but rendered normally for comparison. - const nonClassificationTileset = new Cesium.Cesium3DTileset({ - url: classificationTilesetUrl, - show: false, - }); - nonClassificationTileset.style = new Cesium.Cesium3DTileStyle({ - color: "rgba(255, 0, 0, 0.5)", - }); - viewer.scene.primitives.add(nonClassificationTileset); + // The same b3dm tileset used for classification, but rendered normally for comparison. + const nonClassificationTileset = await Cesium.Cesium3DTileset.fromUrl( + classificationTilesetUrl, + { + show: false, + } + ); + nonClassificationTileset.style = new Cesium.Cesium3DTileStyle({ + color: "rgba(255, 0, 0, 0.5)", + }); + viewer.scene.primitives.add(nonClassificationTileset); - Sandcastle.addToggleButton("Show classification", true, function ( - checked - ) { - classificationTileset.show = checked; - nonClassificationTileset.show = !checked; - }); //Sandcastle_End + Sandcastle.addToggleButton("Show classification", true, function ( + checked + ) { + classificationTileset.show = checked; + nonClassificationTileset.show = !checked; + }); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html index ca2b82815209..7bfde7dcc8f4 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html +++ b/Apps/Sandcastle/gallery/3D Tiles Photogrammetry.html @@ -40,12 +40,13 @@ terrain: Cesium.Terrain.fromWorldTerrain(), }); - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(40866), - }); - - viewer.scene.primitives.add(tileset); - viewer.zoomTo(tileset); //Sandcastle_End + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(40866); + viewer.scene.primitives.add(tileset); + viewer.zoomTo(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Classification.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Classification.html index d07104ec5859..9b4728381221 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Classification.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Classification.html @@ -41,35 +41,38 @@ terrain: Cesium.Terrain.fromWorldTerrain(), }); - //Point Cloud by Prof. Peter Allen, Columbia University Robotics Lab. Scanning by Alejandro Troccoli and Matei Ciocarlie. - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(16421), - }); - viewer.scene.primitives.add(tileset); + try { + // Point Cloud by Prof. Peter Allen, Columbia University Robotics Lab. Scanning by Alejandro Troccoli and Matei Ciocarlie. + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(16421); + viewer.scene.primitives.add(tileset); - // Geometry Tiles are experimental and the format is subject to change in the future. - // For more details, see: - // https://github.com/CesiumGS/3d-tiles/tree/vctr/TileFormats/Geometry - const classificationTileset = new Cesium.Cesium3DTileset({ - url: + // Geometry Tiles are experimental and the format is subject to change in the future. + // For more details, see: + // https://github.com/CesiumGS/3d-tiles/tree/vctr/TileFormats/Geometry + const classificationTileset = await Cesium.Cesium3DTileset.fromUrl( "../../SampleData/Cesium3DTiles/Classification/PointCloud/tileset.json", - classificationType: Cesium.ClassificationType.CESIUM_3D_TILE, - }); - viewer.scene.primitives.add(classificationTileset); + { + classificationType: Cesium.ClassificationType.CESIUM_3D_TILE, + } + ); + viewer.scene.primitives.add(classificationTileset); - classificationTileset.style = new Cesium.Cesium3DTileStyle({ - color: { - conditions: [ - ["${id} === 'roof1'", "color('#004FFF', 0.5)"], - ["${id} === 'towerBottom1'", "color('#33BB66', 0.5)"], - ["${id} === 'towerTop1'", "color('#0099AA', 0.5)"], - ["${id} === 'roof2'", "color('#004FFF', 0.5)"], - ["${id} === 'tower3'", "color('#FF8833', 0.5)"], - ["${id} === 'tower4'", "color('#FFAA22', 0.5)"], - ["true", "color('#FFFF00', 0.5)"], - ], - }, - }); + classificationTileset.style = new Cesium.Cesium3DTileStyle({ + color: { + conditions: [ + ["${id} === 'roof1'", "color('#004FFF', 0.5)"], + ["${id} === 'towerBottom1'", "color('#33BB66', 0.5)"], + ["${id} === 'towerTop1'", "color('#0099AA', 0.5)"], + ["${id} === 'roof2'", "color('#004FFF', 0.5)"], + ["${id} === 'tower3'", "color('#FF8833', 0.5)"], + ["${id} === 'tower4'", "color('#FFAA22', 0.5)"], + ["true", "color('#FFFF00', 0.5)"], + ], + }, + }); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } viewer.scene.camera.setView({ destination: new Cesium.Cartesian3( diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html index cdc3ed6fc5d7..1beeba7bff99 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html @@ -207,71 +207,83 @@ pointCloudShading.eyeDomeLightingRadius; } - function loadStHelens() { - // Set the initial camera view to look at Mt. St. Helens - const initialPosition = Cesium.Cartesian3.fromRadians( - -2.1344873183780484, - 0.8071380277370774, - 5743.394497982162 - ); - const initialOrientation = new Cesium.HeadingPitchRoll.fromDegrees( - 112.99596671210358, - -21.34390550872461, - 0.0716951918898415 - ); - viewer.scene.camera.setView({ - destination: initialPosition, - orientation: initialOrientation, - endTransform: Cesium.Matrix4.IDENTITY, - }); + async function loadStHelens() { + try { + // Set the initial camera view to look at Mt. St. Helens + const initialPosition = Cesium.Cartesian3.fromRadians( + -2.1344873183780484, + 0.8071380277370774, + 5743.394497982162 + ); + const initialOrientation = new Cesium.HeadingPitchRoll.fromDegrees( + 112.99596671210358, + -21.34390550872461, + 0.0716951918898415 + ); + viewer.scene.camera.setView({ + destination: initialPosition, + orientation: initialOrientation, + endTransform: Cesium.Matrix4.IDENTITY, + }); - // Mt. St. Helens 3D Tileset generated from LAS provided by https://www.liblas.org/samples/ - // This tileset uses replacement refinement and has geometric error approximately equal to - // the average interpoint distance in each tile. - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(5713), - }); - viewer.scene.primitives.add(tileset); + // Mt. St. Helens 3D Tileset generated from LAS provided by https://www.liblas.org/samples/ + // This tileset uses replacement refinement and has geometric error approximately equal to + // the average interpoint distance in each tile. + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(5713); + if (viewModel.currentExampleType !== pointClouds[0]) { + // Scenario has changes. Discards the result. + return; + } + viewer.scene.primitives.add(tileset); - tileset.maximumScreenSpaceError = 16.0; - tileset.pointCloudShading.maximumAttenuation = undefined; // Will be based on maximumScreenSpaceError instead - tileset.pointCloudShading.baseResolution = undefined; - tileset.pointCloudShading.geometricErrorScale = 1.0; - tileset.pointCloudShading.attenuation = true; - tileset.pointCloudShading.eyeDomeLighting = true; + tileset.maximumScreenSpaceError = 16.0; + tileset.pointCloudShading.maximumAttenuation = undefined; // Will be based on maximumScreenSpaceError instead + tileset.pointCloudShading.baseResolution = undefined; + tileset.pointCloudShading.geometricErrorScale = 1.0; + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.eyeDomeLighting = true; - tilesetToViewModel(tileset); + tilesetToViewModel(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } } - function loadChurch() { - // Point Cloud by Prof. Peter Allen, Columbia University Robotics Lab. Scanning by Alejandro Troccoli and Matei Ciocarlie. - // This tileset uses additive refinement and has geometric error based on the bounding box size for each tile. - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(16421), - }); - viewer.scene.primitives.add(tileset); + async function loadChurch() { + try { + // Point Cloud by Prof. Peter Allen, Columbia University Robotics Lab. Scanning by Alejandro Troccoli and Matei Ciocarlie. + // This tileset uses additive refinement and has geometric error based on the bounding box size for each tile. + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(16421); + if (viewModel.currentExampleType !== pointClouds[1]) { + // Scenario has changes. Discards the result. + return; + } + viewer.scene.primitives.add(tileset); - tileset.maximumScreenSpaceError = 16.0; - tileset.pointCloudShading.maximumAttenuation = 4.0; // Don't allow points larger than 4 pixels. - tileset.pointCloudShading.baseResolution = 0.05; // Assume an original capture resolution of 5 centimeters between neighboring points. - tileset.pointCloudShading.geometricErrorScale = 0.5; // Applies to both geometric error and the base resolution. - tileset.pointCloudShading.attenuation = true; - tileset.pointCloudShading.eyeDomeLighting = true; + tileset.maximumScreenSpaceError = 16.0; + tileset.pointCloudShading.maximumAttenuation = 4.0; // Don't allow points larger than 4 pixels. + tileset.pointCloudShading.baseResolution = 0.05; // Assume an original capture resolution of 5 centimeters between neighboring points. + tileset.pointCloudShading.geometricErrorScale = 0.5; // Applies to both geometric error and the base resolution. + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.eyeDomeLighting = true; - viewer.scene.camera.setView({ - destination: new Cesium.Cartesian3( - 4401744.644145314, - 225051.41078911052, - 4595420.374784433 - ), - orientation: new Cesium.HeadingPitchRoll( - 5.646733805039757, - -0.276607153839886, - 6.281110875400085 - ), - }); + viewer.scene.camera.setView({ + destination: new Cesium.Cartesian3( + 4401744.644145314, + 225051.41078911052, + 4595420.374784433 + ), + orientation: new Cesium.HeadingPitchRoll( + 5.646733805039757, + -0.276607153839886, + 6.281110875400085 + ), + }); - tilesetToViewModel(tileset); + tilesetToViewModel(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } } function checkZero(newValue) { diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html index c0d8b25038f1..0b9882c09485 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html @@ -42,12 +42,16 @@ const viewer = new Cesium.Viewer("cesiumContainer"); viewer.clock.currentTime = new Cesium.JulianDate(2457522.154792); - const tileset = new Cesium.Cesium3DTileset({ - url: - "../../SampleData/Cesium3DTiles/PointCloud/PointCloudWithPerPointProperties/tileset.json", - }); - viewer.scene.primitives.add(tileset); - viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -1.0, 50.0)); + let tileset; + try { + tileset = await Cesium.Cesium3DTileset.fromUrl( + "../../SampleData/Cesium3DTiles/PointCloud/PointCloudWithPerPointProperties/tileset.json" + ); + viewer.scene.primitives.add(tileset); + viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -1.0, 50.0)); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } const styles = []; function addStyle(name, style) { @@ -219,6 +223,9 @@ function setStyle(style) { return function () { + if (!Cesium.defined(tileset)) { + return; + } tileset.style = new Cesium.Cesium3DTileStyle(style); }; } diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html index 2d5fa1d21e64..10c52266ef4c 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud.html @@ -41,11 +41,6 @@ terrain: Cesium.Terrain.fromWorldTerrain(), }); - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(16421), - }); - viewer.scene.primitives.add(tileset); - viewer.scene.camera.setView({ destination: new Cesium.Cartesian3( 4401744.644145314, @@ -57,7 +52,14 @@ -0.276607153839886, 6.281110875400085 ), - }); //Sandcastle_End + }); + + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(16421); + viewer.scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Apps/Sandcastle/gallery/3D Tiles Terrain Classification.html b/Apps/Sandcastle/gallery/3D Tiles Terrain Classification.html index ef9ea1301f15..086aeaba8e03 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Terrain Classification.html +++ b/Apps/Sandcastle/gallery/3D Tiles Terrain Classification.html @@ -45,17 +45,19 @@ geocoder.flightDuration = 0.0; geocoder.search(); - // Vector 3D Tiles are experimental and the format is subject to change in the future. - // For more details, see: - // https://github.com/CesiumGS/3d-tiles/tree/vctr/TileFormats/VectorData - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(5737), - }); - viewer.scene.primitives.add(tileset); + try { + // Vector 3D Tiles are experimental and the format is subject to change in the future. + // For more details, see: + // https://github.com/CesiumGS/3d-tiles/tree/vctr/TileFormats/VectorData + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(5737); + viewer.scene.primitives.add(tileset); - tileset.style = new Cesium.Cesium3DTileStyle({ - color: "rgba(255, 255, 255, 0.5)", - }); + tileset.style = new Cesium.Cesium3DTileStyle({ + color: "rgba(255, 255, 255, 0.5)", + }); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } // Information about the currently highlighted feature const highlighted = { diff --git a/Apps/Sandcastle/gallery/Aerometrex San Francisco.html b/Apps/Sandcastle/gallery/Aerometrex San Francisco.html index ad722934e5ed..c90af96ea9eb 100755 --- a/Apps/Sandcastle/gallery/Aerometrex San Francisco.html +++ b/Apps/Sandcastle/gallery/Aerometrex San Francisco.html @@ -32,20 +32,18 @@

    Loading...

    diff --git a/Apps/Sandcastle/gallery/Ambient Occlusion.html b/Apps/Sandcastle/gallery/Ambient Occlusion.html index 93d0363738fa..95b5f26543e0 100644 --- a/Apps/Sandcastle/gallery/Ambient Occlusion.html +++ b/Apps/Sandcastle/gallery/Ambient Occlusion.html @@ -129,18 +129,6 @@ ); } - // Power Plant design model provided by Bentley Systems - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(1240402), - }); - tileset.readyPromise - .then(function (tileset) { - viewer.scene.primitives.add(tileset); - }) - .catch(function (error) { - console.log(error); - }); - const viewModel = { show: true, ambientOcclusionOnly: false, @@ -202,7 +190,13 @@ new Cesium.Cartesian3() ); - //Sandcastle_End + try { + // Power Plant design model provided by Bentley Systems + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(1240402); + viewer.scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Apps/Sandcastle/gallery/Clamp to 3D Tiles.html b/Apps/Sandcastle/gallery/Clamp to 3D Tiles.html index ee38cdbfed97..dadb13795bf0 100644 --- a/Apps/Sandcastle/gallery/Clamp to 3D Tiles.html +++ b/Apps/Sandcastle/gallery/Clamp to 3D Tiles.html @@ -51,12 +51,6 @@ positionProperty = entity.position; }); - const tileset = scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(40866), - }) - ); - viewer.camera.setView({ destination: new Cesium.Cartesian3( 1216403.8845586285, @@ -71,10 +65,17 @@ endTransform: Cesium.Matrix4.IDENTITY, }); - if (scene.clampToHeightSupported) { - tileset.initialTilesLoaded.addEventListener(start); - } else { - window.alert("This browser does not support clampToHeight."); + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(40866); + viewer.scene.primitives.add(tileset); + + if (scene.clampToHeightSupported) { + tileset.initialTilesLoaded.addEventListener(start); + } else { + window.alert("This browser does not support clampToHeight."); + } + } catch (error) { + console.log(`Error loading tileset: ${error}`); } function start() { diff --git a/Apps/Sandcastle/gallery/Classification Types.html b/Apps/Sandcastle/gallery/Classification Types.html index 2e285c956b3d..e7db822b24fe 100644 --- a/Apps/Sandcastle/gallery/Classification Types.html +++ b/Apps/Sandcastle/gallery/Classification Types.html @@ -39,12 +39,10 @@ terrain: Cesium.Terrain.fromWorldTerrain(), }); - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(40866), - }); - viewer.scene.primitives.add(tileset); + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(40866); + viewer.scene.primitives.add(tileset); - tileset.readyPromise.then(function () { const boundingSphere = tileset.boundingSphere; viewer.camera.viewBoundingSphere( boundingSphere, @@ -55,7 +53,9 @@ ) ); viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); - }); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } const polygon = viewer.entities.add({ polygon: { diff --git a/Apps/Sandcastle/gallery/Classification.html b/Apps/Sandcastle/gallery/Classification.html index 092fd1256319..ea896e4a2c3a 100644 --- a/Apps/Sandcastle/gallery/Classification.html +++ b/Apps/Sandcastle/gallery/Classification.html @@ -311,10 +311,12 @@ scene.invertClassificationColor.alpha = parseFloat(value); } - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(40866), - }); - scene.primitives.add(tileset); + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(40866); + scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } const viewModel = { inverted: viewer.scene.invertClassification, diff --git a/Apps/Sandcastle/gallery/Custom Shaders 3D Tiles.html b/Apps/Sandcastle/gallery/Custom Shaders 3D Tiles.html index 6347bd97b1fb..41ff38b556e9 100644 --- a/Apps/Sandcastle/gallery/Custom Shaders 3D Tiles.html +++ b/Apps/Sandcastle/gallery/Custom Shaders 3D Tiles.html @@ -44,21 +44,25 @@ //Sandcastle_Begin const viewer = new Cesium.Viewer("cesiumContainer"); - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(75343), - customShader: new Cesium.CustomShader({ - lightingModel: Cesium.LightingModel.UNLIT, - fragmentShaderText: ` - // Color tiles by distance to the camera - void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) - { - material.diffuse = vec3(0.0, 0.0, 1.0); - material.diffuse.g = -fsInput.attributes.positionEC.z / 1.0e4; - } - `, - }), - }); - viewer.scene.primitives.add(tileset); + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(75343, { + customShader: new Cesium.CustomShader({ + lightingModel: Cesium.LightingModel.UNLIT, + fragmentShaderText: ` + // Color tiles by distance to the camera + void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) + { + material.diffuse = vec3(0.0, 0.0, 1.0); + material.diffuse.g = -fsInput.attributes.positionEC.z / 1.0e4; + } + `, + }), + }); + viewer.scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } + const initialPosition = Cesium.Cartesian3.fromDegrees( -74.01881302800248, 40.69114333714821, diff --git a/Apps/Sandcastle/gallery/Custom Shaders Property Textures.html b/Apps/Sandcastle/gallery/Custom Shaders Property Textures.html index 0a556bff3805..134bef94f81b 100644 --- a/Apps/Sandcastle/gallery/Custom Shaders Property Textures.html +++ b/Apps/Sandcastle/gallery/Custom Shaders Property Textures.html @@ -48,15 +48,16 @@ const scene = viewer.scene; scene.globe.depthTestAgainstTerrain = false; - // MAXAR OWT Muscatatuk photogrammetry dataset with property textures - // containing horizontal and vertical uncertainty - const tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(905848), - }) - ); - - viewer.zoomTo(tileset); + let tileset; + try { + // MAXAR OWT Muscatatuk photogrammetry dataset with property textures + // containing horizontal and vertical uncertainty + tileset = await Cesium.Cesium3DTileset.fromIonAssetId(905848); + viewer.scene.primitives.add(tileset); + viewer.zoomTo(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } const shaders = { NO_TEXTURE: undefined, diff --git a/Apps/Sandcastle/gallery/FXAA.html b/Apps/Sandcastle/gallery/FXAA.html index 00e94e1f1b1a..628cdcfa7267 100644 --- a/Apps/Sandcastle/gallery/FXAA.html +++ b/Apps/Sandcastle/gallery/FXAA.html @@ -50,17 +50,18 @@ endTransform: Cesium.Matrix4.IDENTITY, }); - viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(75343), - }) - ); - viewer.scene.postProcessStages.fxaa.enabled = true; Sandcastle.addToggleButton("FXAA", true, function (checked) { viewer.scene.postProcessStages.fxaa.enabled = checked; - }); //Sandcastle_End + }); + + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(75343); + viewer.scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Apps/Sandcastle/gallery/Fog Post Process.html b/Apps/Sandcastle/gallery/Fog Post Process.html index e4e2ad87b520..7adaf7be5f83 100644 --- a/Apps/Sandcastle/gallery/Fog Post Process.html +++ b/Apps/Sandcastle/gallery/Fog Post Process.html @@ -34,16 +34,6 @@ shouldAnimate: true, }); - if (!viewer.scene.context.depthTexture) { - window.alert("This browser does not support the fog post process."); - } - - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(40866), - }); - - viewer.scene.primitives.add(tileset); - viewer.scene.camera.setView({ destination: new Cesium.Cartesian3( 1216356.033078094, @@ -58,6 +48,10 @@ endTransform: Cesium.Matrix4.IDENTITY, }); + if (!viewer.scene.context.depthTexture) { + window.alert("This browser does not support the fog post process."); + } + const fragmentShaderSource = ` float getDistance(sampler2D depthTexture, vec2 texCoords) { @@ -105,7 +99,14 @@ fogColor: Cesium.Color.BLACK, }, }) - ); //Sandcastle_End + ); + + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(40866); + viewer.scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Apps/Sandcastle/gallery/MSAA.html b/Apps/Sandcastle/gallery/MSAA.html index 16a13ca2bd2d..d7a635f70101 100644 --- a/Apps/Sandcastle/gallery/MSAA.html +++ b/Apps/Sandcastle/gallery/MSAA.html @@ -79,6 +79,26 @@ viewer.scene.camera.lookAt(target, offset); } + let currentAssetId; + async function createTileset(assetId) { + currentAssetId = assetId; + + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId( + assetId + ); + + if (currentAssetId !== assetId) { + // Another scenario was loaded. Discard the result. + return; + } + + scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } + } + const options = [ { text: "Statue of Liberty", @@ -98,12 +118,7 @@ ), endTransform: Cesium.Matrix4.IDENTITY, }); - - scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(75343), - }) - ); + createTileset(75343); }, }, { @@ -111,11 +126,6 @@ onselect: function () { viewer.entities.removeAll(); scene.primitives.removeAll(); - const tileset = scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(1240402), - }) - ); viewer.camera.setView({ destination: new Cesium.Cartesian3( 1234138.7804841248, @@ -128,11 +138,13 @@ roll: 6.283184816241989, }, }); + createTileset(1240402); }, }, { text: "Hot Air Balloon", onselect: function () { + currentAssetId = undefined; viewer.entities.removeAll(); scene.primitives.removeAll(); createModel( diff --git a/Apps/Sandcastle/gallery/Montreal Point Cloud.html b/Apps/Sandcastle/gallery/Montreal Point Cloud.html index 77d78a50e7dc..56fca3c6e2d9 100644 --- a/Apps/Sandcastle/gallery/Montreal Point Cloud.html +++ b/Apps/Sandcastle/gallery/Montreal Point Cloud.html @@ -90,17 +90,6 @@ terrain: Cesium.Terrain.fromWorldTerrain(), }); - // A ~10 billion point 3D Tileset of the city of Montreal, Canada captured in 2015 with a resolution of 20 cm. Tiled and hosted by Cesium ion. - const tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(28945), - pointCloudShading: { - attenuation: true, - maximumAttenuation: 2, - }, - }) - ); - // Fly to a nice overview of the city. viewer.camera.flyTo({ destination: new Cesium.Cartesian3( @@ -363,8 +352,22 @@ tileset.style = new Cesium.Cesium3DTileStyle(styleObject); } - // Apply an initial style. - applyStyle(tileset, pointStyles); + let tileset; + try { + // A ~10 billion point 3D Tileset of the city of Montreal, Canada captured in 2015 with a resolution of 20 cm. Tiled and hosted by Cesium ion. + tileset = await Cesium.Cesium3DTileset.fromIonAssetId(28945, { + pointCloudShading: { + attenuation: true, + maximumAttenuation: 2, + }, + }); + viewer.scene.primitives.add(tileset); + + // Apply an initial style. + applyStyle(tileset, pointStyles); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } Cesium.knockout.track(viewModel); diff --git a/Apps/Sandcastle/gallery/Polylines on 3D Tiles.html b/Apps/Sandcastle/gallery/Polylines on 3D Tiles.html index 304324bbcbaf..192f4c3ef3be 100644 --- a/Apps/Sandcastle/gallery/Polylines on 3D Tiles.html +++ b/Apps/Sandcastle/gallery/Polylines on 3D Tiles.html @@ -43,12 +43,18 @@ "2022-08-01T00:00:00Z" ); - const powerplant = scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(1240402), - show: false, - }) - ); + let powerPlant; + let powerPlantShow = false; + try { + powerPlant = await Cesium.Cesium3DTileset.fromIonAssetId(1240402); + powerPlant.show = powerPlantShow; + scene.primitives.add(powerPlant); + powerPlant.tileLoad.addEventListener(function (tile) { + processTileFeatures(tile, hideDuplicateFloor); + }); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } // Hide duplicate floor geometry to avoid // z-fighting artifacts. @@ -86,9 +92,6 @@ } } - powerplant.tileLoad.addEventListener(function (tile) { - processTileFeatures(tile, hideDuplicateFloor); - }); const pipes = viewer.entities.add({ polyline: { positions: Cesium.Cartesian3.fromDegreesArray([ @@ -110,11 +113,15 @@ }, }); - const building = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(40866), - }) - ); + let building; + try { + building = await Cesium.Cesium3DTileset.fromIonAssetId(40866); + building.show = !powerPlantShow; + scene.primitives.add(building); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } + const route = viewer.entities.add({ polyline: { positions: Cesium.Cartesian3.fromDegreesArray([ @@ -143,10 +150,17 @@ { text: "BIM", onselect: function () { - building.show = false; - route.polyline.show = false; - powerplant.show = true; - pipes.polyline.show = true; + powerPlantShow = true; + + pipes.polyline.show = powerPlantShow; + route.polyline.show = !powerPlantShow; + if (Cesium.defined(powerPlant)) { + powerPlant.show = powerPlantShow; + } + if (Cesium.defined(building)) { + building.show = !powerPlantShow; + } + scene.camera.setView({ destination: new Cesium.Cartesian3( 1234151.4883992162, @@ -164,10 +178,17 @@ { text: "Photogrammetry", onselect: function () { - building.show = true; - route.polyline.show = true; - powerplant.show = false; - pipes.polyline.show = false; + powerPlantShow = false; + + pipes.polyline.show = powerPlantShow; + route.polyline.show = !powerPlantShow; + if (Cesium.defined(powerPlant)) { + powerPlant.show = powerPlantShow; + } + if (Cesium.defined(building)) { + building.show = !powerPlantShow; + } + scene.camera.setView({ destination: new Cesium.Cartesian3( 1216596.5376729995, diff --git a/Apps/Sandcastle/gallery/Sample Height from 3D Tiles.html b/Apps/Sandcastle/gallery/Sample Height from 3D Tiles.html index 5e4c6b5d6aeb..31a6c0efbd2f 100644 --- a/Apps/Sandcastle/gallery/Sample Height from 3D Tiles.html +++ b/Apps/Sandcastle/gallery/Sample Height from 3D Tiles.html @@ -46,12 +46,6 @@ ); } - const tileset = scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(40866), - }) - ); - scene.camera.setView({ destination: new Cesium.Cartesian3( 1216411.0748779264, @@ -66,6 +60,13 @@ endTransform: Cesium.Matrix4.IDENTITY, }); + try { + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(40866); + scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } + Sandcastle.addToolbarButton("Sample heights", function () { sampleHeights(); }); diff --git a/Apps/Sandcastle/gallery/Scene Rendering Performance.html b/Apps/Sandcastle/gallery/Scene Rendering Performance.html index bc865eedbe70..028cb0f6ac9f 100644 --- a/Apps/Sandcastle/gallery/Scene Rendering Performance.html +++ b/Apps/Sandcastle/gallery/Scene Rendering Performance.html @@ -167,6 +167,7 @@

    Max delta time

    // Clear scene and set default view. let handler; + let loadingTileset = false; function resetScene() { viewer.trackedEntity = undefined; viewer.dataSources.removeAll(); @@ -180,18 +181,26 @@

    Max delta time

    viewModel.showTimeOptions = false; viewModel.timeChangeEnabled = false; viewModel.maximumRenderTimeChange = 0; + loadingTileset = false; } // Load a tileset and set the view. // No need to call scene.requestRender() - function loadTilesetScenario() { + async function loadTilesetScenario() { resetScene(); - tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(40866), - }); - viewer.scene.primitives.add(tileset); - viewer.zoomTo(tileset); + loadingTileset = true; + try { + tileset = await Cesium.Cesium3DTileset.fromIonAssetId(40866); + if (!loadingTileset) { + // Scenario was changed. Discard result. + return; + } + viewer.scene.primitives.add(tileset); + viewer.zoomTo(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } } // Load an animated model and set the view. diff --git a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html index fe9b60f63390..a31336922fad 100644 --- a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html @@ -155,7 +155,7 @@ viewer.trackedEntity = entity; } - function loadStHelens() { + async function loadStHelens() { // Create clipping planes for polygon around area to be clipped. const points = [ new Cesium.Cartesian3( @@ -283,51 +283,51 @@ globe.backFaceCulling = true; globe.showSkirts = true; - // Load tileset - tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(5713), - }); - return tileset.readyPromise - .then(function () { - // Adjust height so tileset is in terrain - const cartographic = Cesium.Cartographic.fromCartesian( - tileset.boundingSphere.center - ); - const surface = Cesium.Cartesian3.fromRadians( - cartographic.longitude, - cartographic.latitude, - 0.0 - ); - const offset = Cesium.Cartesian3.fromRadians( - cartographic.longitude, - cartographic.latitude, - -20.0 - ); - const translation = Cesium.Cartesian3.subtract( - offset, - surface, - new Cesium.Cartesian3() - ); - tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); + try { + // Load tileset + tileset = await Cesium.Cesium3DTileset.fromIonAssetId(5713); + if (viewModel.currentExampleType !== exampleTypes[1]) { + // Scenario has changed. Discard the result. + return; + } + // Adjust height so tileset is in terrain + const cartographic = Cesium.Cartographic.fromCartesian( + tileset.boundingSphere.center + ); + const surface = Cesium.Cartesian3.fromRadians( + cartographic.longitude, + cartographic.latitude, + 0.0 + ); + const offset = Cesium.Cartesian3.fromRadians( + cartographic.longitude, + cartographic.latitude, + -20.0 + ); + const translation = Cesium.Cartesian3.subtract( + offset, + surface, + new Cesium.Cartesian3() + ); + tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); - tileset.style = new Cesium.Cesium3DTileStyle({ - color: "rgb(207, 255, 207)", - }); + tileset.style = new Cesium.Cesium3DTileStyle({ + color: "rgb(207, 255, 207)", + }); - viewer.scene.primitives.add(tileset); + viewer.scene.primitives.add(tileset); - const boundingSphere = tileset.boundingSphere; + const boundingSphere = tileset.boundingSphere; - const radius = boundingSphere.radius; - viewer.camera.viewBoundingSphere( - boundingSphere, - new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0) - ); - viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); - }) - .catch(function (error) { - throw error; - }); + const radius = boundingSphere.radius; + viewer.camera.viewBoundingSphere( + boundingSphere, + new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0) + ); + viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } } function loadGrandCanyon() { diff --git a/Apps/Sandcastle/gallery/development/3D Tiles Performance Testing.html b/Apps/Sandcastle/gallery/development/3D Tiles Performance Testing.html index 7748ce8edace..4fdd86f97fb7 100644 --- a/Apps/Sandcastle/gallery/development/3D Tiles Performance Testing.html +++ b/Apps/Sandcastle/gallery/development/3D Tiles Performance Testing.html @@ -64,8 +64,7 @@ const referenceMaximum = new Cesium.JulianDate(); const heatmapTileProperty = "_foveatedFactor"; - const tileset = new Cesium.Cesium3DTileset({ - url: Cesium.IonResource.fromAssetId(75343), + const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(75343, { debugHeatmapTilePropertyName: heatmapTileProperty, }); diff --git a/Apps/Sandcastle/gallery/development/3D Tiles Split.html b/Apps/Sandcastle/gallery/development/3D Tiles Split.html index 0eb533860398..0ec89e25de08 100644 --- a/Apps/Sandcastle/gallery/development/3D Tiles Split.html +++ b/Apps/Sandcastle/gallery/development/3D Tiles Split.html @@ -182,63 +182,62 @@ } }); + let loadedResource; Cesium.knockout .getObservable(viewModel, "selectedTileset") - .subscribe(function (options) { + .subscribe(async function (options) { if (Cesium.defined(tileset)) { scene.primitives.remove(tileset); } if (!Cesium.defined(options)) { inspectorViewModel.tileset = undefined; + loadedResource = undefined; return; } - tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: options.resource, - enableDebugWireframe: true, - }) - ); + loadedResource = options.resource; + tileset = await Cesium.Cesium3DTileset.fromUrl(options.resource, { + enableDebugWireframe: true, + }); + if (loadedResource !== options.resource) { + // Another scenario was loaded. Discard result. + return; + } + viewer.scene.primitives.add(tileset); if (Cesium.defined(viewModel.selectedSplitDirection)) { tileset.splitDirection = viewModel.selectedSplitDirection.value; } - tileset.readyPromise - .then(function () { - inspectorViewModel.tileset = tileset; - viewer.zoomTo( - tileset, - new Cesium.HeadingPitchRange( - 0, - -2.0, - Math.max(100.0 - tileset.boundingSphere.radius, 0.0) - ) - ); + inspectorViewModel.tileset = tileset; + viewer.zoomTo( + tileset, + new Cesium.HeadingPitchRange( + 0, + -2.0, + Math.max(100.0 - tileset.boundingSphere.radius, 0.0) + ) + ); - const properties = tileset.properties; - if ( - Cesium.defined(properties) && - Cesium.defined(properties.Height) - ) { - tileset.style = new Cesium.Cesium3DTileStyle({ - color: { - conditions: [ - ["${Height} >= 83", "color('purple', 0.5)"], - ["${Height} >= 80", "color('red')"], - ["${Height} >= 70", "color('orange')"], - ["${Height} >= 12", "color('yellow')"], - ["${Height} >= 7", "color('lime')"], - ["${Height} >= 1", "color('cyan')"], - ["true", "color('blue')"], - ], - }, - }); - } - }) - .catch(function (error) { - throw error; + const properties = tileset.properties; + if ( + Cesium.defined(properties) && + Cesium.defined(properties.Height) + ) { + tileset.style = new Cesium.Cesium3DTileStyle({ + color: { + conditions: [ + ["${Height} >= 83", "color('purple', 0.5)"], + ["${Height} >= 80", "color('red')"], + ["${Height} >= 70", "color('orange')"], + ["${Height} >= 12", "color('yellow')"], + ["${Height} >= 7", "color('lime')"], + ["${Height} >= 1", "color('cyan')"], + ["true", "color('blue')"], + ], + }, }); + } }); viewModel.selectedTileset = viewModel.tilesets[0]; diff --git a/Apps/Sandcastle/gallery/development/Many Clipping Planes.html b/Apps/Sandcastle/gallery/development/Many Clipping Planes.html index e5cc49ba2b9c..79ef45f31501 100644 --- a/Apps/Sandcastle/gallery/development/Many Clipping Planes.html +++ b/Apps/Sandcastle/gallery/development/Many Clipping Planes.html @@ -217,17 +217,20 @@ } let tileset; + let currentUrl; async function loadTileset(url, height) { + currentUrl = url; createClippingPlanes(); - tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: url, - clippingPlanes: modelEntityClippingPlanes, - enableDebugWireframe: true, - }) - ); + tileset = await Cesium.Cesium3DTileset.fromUrl(url, { + clippingPlanes: modelEntityClippingPlanes, + enableDebugWireframe: true, + }); + if (currentUrl !== url) { + // Another scenario has been loaded. Discard the result. + return; + } + viewer.scene.primitives.add(tileset); - await tileset.readyPromise; const boundingSphere = tileset.boundingSphere; const cartographic = Cesium.Cartographic.fromCartesian( diff --git a/Apps/Sandcastle/gallery/development/Pick From Ray.html b/Apps/Sandcastle/gallery/development/Pick From Ray.html index a24c5a563f99..98a720f1e535 100644 --- a/Apps/Sandcastle/gallery/development/Pick From Ray.html +++ b/Apps/Sandcastle/gallery/development/Pick From Ray.html @@ -49,11 +49,10 @@ drillPick = checked; }); - const tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: "../../SampleData/Cesium3DTiles/Tilesets/Tileset/tileset.json", - }) + const tileset = await Cesium.Cesium3DTileset.fromUrl( + "../../SampleData/Cesium3DTiles/Tilesets/Tileset/tileset.json" ); + viewer.scene.primitives.add(tileset); tileset.style = new Cesium.Cesium3DTileStyle({ defines: { diff --git a/Apps/Sandcastle/gallery/development/Picking.html b/Apps/Sandcastle/gallery/development/Picking.html index 2c8c309193e2..740c7a55d9de 100644 --- a/Apps/Sandcastle/gallery/development/Picking.html +++ b/Apps/Sandcastle/gallery/development/Picking.html @@ -53,6 +53,7 @@ const worldTerrain = Cesium.Terrain.fromWorldTerrain(); const ellipsoidTerrainProvider = new Cesium.EllipsoidTerrainProvider(); + let loadedResource; Sandcastle.addToolbarMenu([ { text: "Billboard", @@ -333,274 +334,247 @@ }, { text: "Batched 3D Model", - onselect: function () { - tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: - "../../SampleData/Cesium3DTiles/Tilesets/Tileset/tileset.json", - }) + onselect: async function () { + const url = + "../../SampleData/Cesium3DTiles/Tilesets/Tileset/tileset.json"; + loadedResource = url; + tileset = await Cesium.Cesium3DTileset.fromUrl(url); + if (loadedResource !== url) { + // Another scenario was loaded. Discard result. + return; + } + viewer.scene.primitives.add(tileset); + viewer.zoomTo( + tileset, + new Cesium.HeadingPitchRange( + 0, + -2.0, + Math.max(100.0 - tileset.boundingSphere.radius, 0.0) + ) ); - tileset.readyPromise - .then(function () { - viewer.zoomTo( - tileset, - new Cesium.HeadingPitchRange( - 0, - -2.0, - Math.max(100.0 - tileset.boundingSphere.radius, 0.0) - ) - ); - - handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); - handler.setInputAction(function (movement) { - if (Cesium.defined(highlighted.feature)) { - highlighted.feature.color = highlighted.originalColor; - highlighted.feature = undefined; - } - const pickedFeature = viewer.scene.pick( - movement.endPosition - ); - if (!Cesium.defined(pickedFeature)) { - return; - } - highlighted.feature = pickedFeature; - Cesium.Color.clone( - pickedFeature.color, - highlighted.originalColor - ); - pickedFeature.color = Cesium.Color.YELLOW; - }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); - }) - .catch(function (error) { - throw error; - }); + handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); + handler.setInputAction(function (movement) { + if (Cesium.defined(highlighted.feature)) { + highlighted.feature.color = highlighted.originalColor; + highlighted.feature = undefined; + } + const pickedFeature = viewer.scene.pick(movement.endPosition); + if (!Cesium.defined(pickedFeature)) { + return; + } + highlighted.feature = pickedFeature; + Cesium.Color.clone( + pickedFeature.color, + highlighted.originalColor + ); + pickedFeature.color = Cesium.Color.YELLOW; + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); }, }, { text: "Instanced 3D Model", - onselect: function () { - tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: - "../../SampleData/Cesium3DTiles/Instanced/InstancedWithBatchTable/tileset.json", - }) - ); + onselect: async function () { + const url = + "../../SampleData/Cesium3DTiles/Instanced/InstancedWithBatchTable/tileset.json"; + loadedResource = url; + tileset = await Cesium.Cesium3DTileset.fromUrl(url); + if (loadedResource !== url) { + // Another scenario was loaded. Discard result. + return; + } + viewer.scene.primitives.add(tileset); - tileset.readyPromise - .then(function () { - viewer.zoomTo( - tileset, - new Cesium.HeadingPitchRange( - 0, - -2.0, - Math.max(100.0 - tileset.boundingSphere.radius, 0.0) - ) - ); + viewer.zoomTo( + tileset, + new Cesium.HeadingPitchRange( + 0, + -2.0, + Math.max(100.0 - tileset.boundingSphere.radius, 0.0) + ) + ); - handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); - handler.setInputAction(function (movement) { - if (Cesium.defined(highlighted.feature)) { - highlighted.feature.color = highlighted.originalColor; - highlighted.feature = undefined; - } - const pickedFeature = viewer.scene.pick( - movement.endPosition - ); - if (!Cesium.defined(pickedFeature)) { - return; - } - highlighted.feature = pickedFeature; - Cesium.Color.clone( - pickedFeature.color, - highlighted.originalColor - ); - pickedFeature.color = Cesium.Color.YELLOW; - }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); - }) - .catch(function (error) { - throw error; - }); + handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); + handler.setInputAction(function (movement) { + if (Cesium.defined(highlighted.feature)) { + highlighted.feature.color = highlighted.originalColor; + highlighted.feature = undefined; + } + const pickedFeature = viewer.scene.pick(movement.endPosition); + if (!Cesium.defined(pickedFeature)) { + return; + } + highlighted.feature = pickedFeature; + Cesium.Color.clone( + pickedFeature.color, + highlighted.originalColor + ); + pickedFeature.color = Cesium.Color.YELLOW; + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); }, }, { text: "Point cloud", - onselect: function () { - tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: - "../../SampleData/Cesium3DTiles/PointCloud/PointCloudRGB/tileset.json", - }) - ); + onselect: async function () { + const url = + "../../SampleData/Cesium3DTiles/PointCloud/PointCloudRGB/tileset.json"; + loadedResource = url; + tileset = await Cesium.Cesium3DTileset.fromUrl(url); + if (loadedResource !== url) { + // Another scenario was loaded. Discard result. + return; + } + viewer.scene.primitives.add(tileset); - tileset.readyPromise - .then(function () { - viewer.zoomTo( - tileset, - new Cesium.HeadingPitchRange( - 0, - -2.0, - Math.max(100.0 - tileset.boundingSphere.radius, 0.0) - ) - ); + viewer.zoomTo( + tileset, + new Cesium.HeadingPitchRange( + 0, + -2.0, + Math.max(100.0 - tileset.boundingSphere.radius, 0.0) + ) + ); - tileset.style = new Cesium.Cesium3DTileStyle({ - pointSize: 8.0, - }); + tileset.style = new Cesium.Cesium3DTileStyle({ + pointSize: 8.0, + }); - handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); - handler.setInputAction(function (movement) { - if (Cesium.defined(highlighted.feature)) { - highlighted.feature.primitive.style = new Cesium.Cesium3DTileStyle( - { - pointSize: 10.0, - } - ); - highlighted.feature = undefined; - } - const pickedFeature = viewer.scene.pick( - movement.endPosition - ); - if (!Cesium.defined(pickedFeature)) { - return; + handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); + handler.setInputAction(function (movement) { + if (Cesium.defined(highlighted.feature)) { + highlighted.feature.primitive.style = new Cesium.Cesium3DTileStyle( + { + pointSize: 10.0, } - highlighted.feature = pickedFeature; - pickedFeature.primitive.style = new Cesium.Cesium3DTileStyle( - { - pointSize: 10.0, - color: 'color("yellow")', - } - ); - }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); - }) - .catch(function (error) { - throw error; + ); + highlighted.feature = undefined; + } + const pickedFeature = viewer.scene.pick(movement.endPosition); + if (!Cesium.defined(pickedFeature)) { + return; + } + highlighted.feature = pickedFeature; + pickedFeature.primitive.style = new Cesium.Cesium3DTileStyle({ + pointSize: 10.0, + color: 'color("yellow")', }); + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); }, }, { text: "Vector tile polygons", - onselect: function () { + onselect: async function () { viewer.scene.setTerrain(worldTerrain); - tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: - "../../../Specs/Data/Cesium3DTiles/Vector/VectorTilePolygons/tileset.json", - }) - ); + const url = + "../../../Specs/Data/Cesium3DTiles/Vector/VectorTilePolygons/tileset.json"; + loadedResource = url; + tileset = await Cesium.Cesium3DTileset.fromUrl(url); + if (loadedResource !== url) { + // Another scenario was loaded. Discard result. + return; + } + viewer.scene.primitives.add(tileset); - tileset.readyPromise - .then(function () { - camera.position = new Cesium.Cartesian3( - 6382696.762766026, - 20.61495686957654, - -83.83598213685399 - ); - camera.direction = new Cesium.Cartesian3( - -0.9999999739409788, - 0.00022792812935066512, - 0.000012915478344419502 - ); - camera.up = new Cesium.Cartesian3( - 0.00001291547800893194, - -2.9438010410026854e-9, - 0.9999999999165953 - ); - camera.right = new Cesium.Cartesian3.cross( - camera.direction, - camera.up, - camera.right - ); + camera.position = new Cesium.Cartesian3( + 6382696.762766026, + 20.61495686957654, + -83.83598213685399 + ); + camera.direction = new Cesium.Cartesian3( + -0.9999999739409788, + 0.00022792812935066512, + 0.000012915478344419502 + ); + camera.up = new Cesium.Cartesian3( + 0.00001291547800893194, + -2.9438010410026854e-9, + 0.9999999999165953 + ); + camera.right = new Cesium.Cartesian3.cross( + camera.direction, + camera.up, + camera.right + ); - handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); - handler.setInputAction(function (movement) { - if (Cesium.defined(highlighted.feature)) { - highlighted.feature.color = highlighted.originalColor; - highlighted.feature = undefined; - } - const pickedFeature = viewer.scene.pick( - movement.endPosition - ); - if (!Cesium.defined(pickedFeature)) { - return; - } - highlighted.feature = pickedFeature; - Cesium.Color.clone( - pickedFeature.color, - highlighted.originalColor - ); - pickedFeature.color = Cesium.Color.YELLOW; - }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); - }) - .catch(function (error) { - throw error; - }); + handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); + handler.setInputAction(function (movement) { + if (Cesium.defined(highlighted.feature)) { + highlighted.feature.color = highlighted.originalColor; + highlighted.feature = undefined; + } + const pickedFeature = viewer.scene.pick(movement.endPosition); + if (!Cesium.defined(pickedFeature)) { + return; + } + highlighted.feature = pickedFeature; + Cesium.Color.clone( + pickedFeature.color, + highlighted.originalColor + ); + pickedFeature.color = Cesium.Color.YELLOW; + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); }, }, { text: "Vector tile polylines", - onselect: function () { + onselect: async function () { viewer.scene.setTerrain(worldTerrain); - tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: - "../../../Specs/Data/Cesium3DTiles/Vector/VectorTilePolylines/tileset.json", - }) - ); + const url = + "../../../Specs/Data/Cesium3DTiles/Vector/VectorTilePolylines/tileset.json"; + loadedResource = url; + tileset = await Cesium.Cesium3DTileset.fromUrl(url); + if (loadedResource !== url) { + // Another scenario was loaded. Discard result. + return; + } + viewer.scene.primitives.add(tileset); - tileset.readyPromise - .then(function () { - camera.position = new Cesium.Cartesian3( - 6382696.762766026, - 20.61495686957654, - -83.83598213685399 - ); - camera.direction = new Cesium.Cartesian3( - -0.9999999739409788, - 0.00022792812935066512, - 0.000012915478344419502 - ); - camera.up = new Cesium.Cartesian3( - 0.00001291547800893194, - -2.9438010410026854e-9, - 0.9999999999165953 - ); - camera.right = new Cesium.Cartesian3.cross( - camera.direction, - camera.up, - camera.right - ); + camera.position = new Cesium.Cartesian3( + 6382696.762766026, + 20.61495686957654, + -83.83598213685399 + ); + camera.direction = new Cesium.Cartesian3( + -0.9999999739409788, + 0.00022792812935066512, + 0.000012915478344419502 + ); + camera.up = new Cesium.Cartesian3( + 0.00001291547800893194, + -2.9438010410026854e-9, + 0.9999999999165953 + ); + camera.right = new Cesium.Cartesian3.cross( + camera.direction, + camera.up, + camera.right + ); - handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); - handler.setInputAction(function (movement) { - if (Cesium.defined(highlighted.feature)) { - highlighted.feature.color = highlighted.originalColor; - highlighted.feature = undefined; - } - const pickedFeature = viewer.scene.pick( - movement.endPosition - ); - if (!Cesium.defined(pickedFeature)) { - return; - } - highlighted.feature = pickedFeature; - Cesium.Color.clone( - pickedFeature.color, - highlighted.originalColor - ); - pickedFeature.color = Cesium.Color.YELLOW; - }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); - }) - .catch(function (error) { - throw error; - }); + handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); + handler.setInputAction(function (movement) { + if (Cesium.defined(highlighted.feature)) { + highlighted.feature.color = highlighted.originalColor; + highlighted.feature = undefined; + } + const pickedFeature = viewer.scene.pick(movement.endPosition); + if (!Cesium.defined(pickedFeature)) { + return; + } + highlighted.feature = pickedFeature; + Cesium.Color.clone( + pickedFeature.color, + highlighted.originalColor + ); + pickedFeature.color = Cesium.Color.YELLOW; + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); }, }, ]); Sandcastle.reset = function () { + loadedResource = undefined; viewer.entities.removeAll(); if (Cesium.defined(polylines)) { scene.primitives.remove(polylines); diff --git a/Apps/Sandcastle/gallery/development/Shadows.html b/Apps/Sandcastle/gallery/development/Shadows.html index f6d39e4adcbe..ad840163f0aa 100644 --- a/Apps/Sandcastle/gallery/development/Shadows.html +++ b/Apps/Sandcastle/gallery/development/Shadows.html @@ -899,18 +899,18 @@ } } - function createTileset(resource) { - resource - .then(function (url) { - viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: url, - }) - ); - }) - .catch(function (error) { - console.log(error); - }); + async function createTileset(url) { + try { + const tileset = await Cesium.Cesium3DTileset.fromUrl(url); + const location = uiOptions.locations[viewModel.location]; + if (location.tileset !== url) { + // Another option was loaded. Discard the result; + return; + } + viewer.scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } } async function createModel(url, origin) { diff --git a/CHANGES.md b/CHANGES.md index 9dff17ef1645..6b282ba90bf8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,7 +19,7 @@ try { ##### Additions :tada: -- Added `ArcGisMapServerImageryProvider.fromUrl`, `ArcGISTiledElevationTerrainProvider.fromUrl`, `BingMapsImageryProvider.fromUrl`, `CesiumTerrainProvider.fromUrl`, `GoogleEarthEnterpriseMetadata.fromUrl`, `GoogleEarthEnterpriseImageryProvider.fromMetadata`, `GoogleEarthEnterpriseMapsProvider.fromUrl`, `GoogleEarthEnterpriseTerrainProvider.fromMetadata`, `ImageryLayer.fromProviderAsync`, `IonImageryProvider.fromAssetId`, `SingleTileImageryProvider.fromUrl`, `Terrain`, `TileMapServiceImageryProvider.fromUrl`, `VRTheWorldTerrainProvider.fromUrl`, `createWorldTerrainAsync`, `Cesium3DTileset.fromUrl`, `createOsmBuildingsAsync`, `Model.fromGltfAsync`, , `Model.readyEvent`, `Model.errorEvent`, and `Model.texturesReadyEvent` for better async flow and error handling. +- Added `ArcGisMapServerImageryProvider.fromUrl`, `ArcGISTiledElevationTerrainProvider.fromUrl`, `BingMapsImageryProvider.fromUrl`, `CesiumTerrainProvider.fromUrl`, `GoogleEarthEnterpriseMetadata.fromUrl`, `GoogleEarthEnterpriseImageryProvider.fromMetadata`, `GoogleEarthEnterpriseMapsProvider.fromUrl`, `GoogleEarthEnterpriseTerrainProvider.fromMetadata`, `ImageryLayer.fromProviderAsync`, `IonImageryProvider.fromAssetId`, `SingleTileImageryProvider.fromUrl`, `Terrain`, `TileMapServiceImageryProvider.fromUrl`, `VRTheWorldTerrainProvider.fromUrl`, `createWorldTerrainAsync`, `Cesium3DTileset.fromUrl`, `Cesium3DTileset.fromIonAssetId`, `createOsmBuildingsAsync`, `Model.fromGltfAsync`, `Model.readyEvent`, `Model.errorEvent`, and `Model.texturesReadyEvent` for better async flow and error handling. ##### Fixes :wrench: diff --git a/Documentation/CustomShaderGuide/README.md b/Documentation/CustomShaderGuide/README.md index 681559dbdf39..714af9604308 100644 --- a/Documentation/CustomShaderGuide/README.md +++ b/Documentation/CustomShaderGuide/README.md @@ -67,10 +67,11 @@ follows: const customShader = new Cesium.CustomShader(/* ... */); // Applying to all tiles in a tileset. -const tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ - url: "http://example.com/tileset.json", - customShader: customShader -})); +const tileset = await Cesium.Cesium3DTileset.fromUrl( + "http://example.com/tileset.json", { + customShader: customShader +}); +viewer.scene.primitives.add(tileset); // Applying to a model directly const model = await Cesium.Model.fromGltfAsync({, diff --git a/Documentation/OfflineGuide/README.md b/Documentation/OfflineGuide/README.md index 9d8a23eed789..e50e84fa1cac 100644 --- a/Documentation/OfflineGuide/README.md +++ b/Documentation/OfflineGuide/README.md @@ -57,9 +57,12 @@ Most other files loaded in CesiumJS, such as 3D Tiles or glTF, are static assets For example, a local tileset in an `example` directory can now be loaded with the following url: ```js - const tileset = viewer.scene.primitives.add( - new Cesium.Cesium3DTileset({ - url: "http://localhost:8003/example/tileset.json", - }) - ); + try { + const tileset = await Cesium.Cesium3DTileset.fromUrl( + "http://localhost:8003/example/tileset.json" + ); + viewer.scene.primitives.add(tileset); + } catch (error) { + console.log(`Error loading tileset: ${error}`); + } ``` diff --git a/packages/engine/Source/Scene/BufferLoader.js b/packages/engine/Source/Scene/BufferLoader.js index 6c1eeb33e771..fb5f572abb9c 100644 --- a/packages/engine/Source/Scene/BufferLoader.js +++ b/packages/engine/Source/Scene/BufferLoader.js @@ -57,6 +57,7 @@ Object.defineProperties(BufferLoader.prototype, { * * @type {string} * @readonly + * @private */ cacheKey: { get: function () { @@ -70,6 +71,7 @@ Object.defineProperties(BufferLoader.prototype, { * * @type {Uint8Array} * @readonly + * @private */ typedArray: { get: function () { diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 3c885a72c8ea..f01a49263e63 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -1969,9 +1969,9 @@ Object.defineProperties(Cesium3DTileset.prototype, { * console.error(`Error creating tileset: ${error}`); * } */ -Cesium3DTileset.fromIonAssetId = async function (url, options) { - const resource = await IonResource.fromAssetId(124624234); - return Cesium3DTileset.fromUrl(resource); +Cesium3DTileset.fromIonAssetId = async function (assetId, options) { + const resource = await IonResource.fromAssetId(assetId); + return Cesium3DTileset.fromUrl(resource, options); }; /** diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index 24db24b33bc9..f2aa0cc0ae90 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -89,6 +89,8 @@ const GltfLoaderState = { * * @type {number} * @constant + * + * @private */ LOADED: 2, /** @@ -97,6 +99,8 @@ const GltfLoaderState = { * * @type {number} * @constant + * + * @private */ PROCESSING: 3, /** @@ -109,6 +113,8 @@ const GltfLoaderState = { * * @type {number} * @constant + * + * @private */ POST_PROCESSING: 4, /** @@ -118,6 +124,8 @@ const GltfLoaderState = { * * @type {number} * @constant + * + * @private */ PROCESSED: 5, /** @@ -126,6 +134,8 @@ const GltfLoaderState = { * * @type {number} * @constant + * + * @private */ READY: 6, /** @@ -133,6 +143,8 @@ const GltfLoaderState = { * * @type {number} * @constant + * + * @private */ FAILED: 7, /** @@ -140,6 +152,8 @@ const GltfLoaderState = { * * @type {number} * @constant + * + * @private */ UNLOADED: 8, }; diff --git a/packages/engine/Source/Scene/ResourceCache.js b/packages/engine/Source/Scene/ResourceCache.js index 21d8e8c4a664..086033589d1f 100644 --- a/packages/engine/Source/Scene/ResourceCache.js +++ b/packages/engine/Source/Scene/ResourceCache.js @@ -308,7 +308,7 @@ ResourceCache.getGltfJsonLoader = function (options) { * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * - * @returns {GltfBufferViewLoader>} The cached buffer view loader. + * @returns {GltfBufferViewLoader} The cached buffer view loader. * @private */ ResourceCache.getBufferViewLoader = function (options) { diff --git a/packages/engine/Source/Scene/Tileset3DTileContent.js b/packages/engine/Source/Scene/Tileset3DTileContent.js index e3ea176125b1..9d5ba1172c9a 100644 --- a/packages/engine/Source/Scene/Tileset3DTileContent.js +++ b/packages/engine/Source/Scene/Tileset3DTileContent.js @@ -119,12 +119,12 @@ Object.defineProperties(Tileset3DTileContent.prototype, { }); /** - * + * Creates an instance of Tileset3DTileContent from a parsed JSON object * @param {Cesium3DTileset} tileset * @param {Cesium3DTile} tile * @param {Resource} resource * @param {object} json - * @returns + * @returns {Tileset3DTileContent} */ Tileset3DTileContent.fromJson = function (tileset, tile, resource, json) { const content = new Tileset3DTileContent(tileset, tile, resource); diff --git a/packages/engine/Source/Scene/createOsmBuildingsAsync.js b/packages/engine/Source/Scene/createOsmBuildingsAsync.js index 8206ce5a1782..08b4b019c7ad 100644 --- a/packages/engine/Source/Scene/createOsmBuildingsAsync.js +++ b/packages/engine/Source/Scene/createOsmBuildingsAsync.js @@ -1,7 +1,6 @@ import Color from "../Core/Color.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; -import IonResource from "../Core/IonResource.js"; import Cesium3DTileset from "./Cesium3DTileset.js"; import Cesium3DTileStyle from "./Cesium3DTileStyle.js"; @@ -59,10 +58,9 @@ import Cesium3DTileStyle from "./Cesium3DTileStyle.js"; * } */ async function createOsmBuildingsAsync(options) { - const tileset = await Cesium3DTileset.fromUrl( - IonResource.fromAssetId(96188), - options - ); + const tileset = await Cesium3DTileset.fromIonAssetId(96188, options); + + options = defaultValue(options, defaultValue.EMPTY_OBJECT); let style = options.style; From e2ae222efeffaf394a5bc7a63b893e28031300cc Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Mon, 20 Mar 2023 17:00:45 -0400 Subject: [PATCH 06/18] Fix specs --- packages/engine/Specs/Scene/Composite3DTileContentSpec.js | 5 +---- .../Cesium3DTilesInspectorViewModelSpec.js | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/engine/Specs/Scene/Composite3DTileContentSpec.js b/packages/engine/Specs/Scene/Composite3DTileContentSpec.js index 2d454a9912d8..2dcea78122bc 100644 --- a/packages/engine/Specs/Scene/Composite3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Composite3DTileContentSpec.js @@ -135,10 +135,7 @@ describe( await expectAsync( Cesium3DTilesTester.createContentForMockTile(arrayBuffer, "cmpt") - ).toBeRejectedWithError( - RuntimeError, - "Failed to load i3dm\nFailed to load glTF\nFailed to load glTF: http://localhost:8080/Specs/invalid?compositeIndex=0" - ); + ).toBeRejectedWithError(RuntimeError); }); it("renders composite", function () { diff --git a/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js b/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js index b38d51013900..d681f7cba70a 100644 --- a/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js +++ b/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js @@ -58,9 +58,7 @@ describe( scene, performanceContainer ); - const tileset = await Cesium3DTileset.fromUrl({ - url: tilesetUrl, - }); + const tileset = await Cesium3DTileset.fromUrl(tilesetUrl); viewModel.tileset = tileset; expect(viewModel.properties.indexOf("id") !== -1).toBe(true); expect(viewModel.properties.indexOf("Longitude") !== -1).toBe(true); From c7d0cc2d972402f9dd99b51383ea7b3d8ae6ea95 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Tue, 21 Mar 2023 09:25:12 -0400 Subject: [PATCH 07/18] Fix spec --- .../Cesium3DTilesInspectorViewModelSpec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js b/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js index d681f7cba70a..33bf3e02879f 100644 --- a/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js +++ b/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js @@ -60,6 +60,8 @@ describe( ); const tileset = await Cesium3DTileset.fromUrl(tilesetUrl); viewModel.tileset = tileset; + // Here to allow backwards compatibility- This immediately resolves for tilesets created with fromUrl(). Can be removed when ready promises are removed. + await tileset.readyPromise; expect(viewModel.properties.indexOf("id") !== -1).toBe(true); expect(viewModel.properties.indexOf("Longitude") !== -1).toBe(true); expect(viewModel.properties.indexOf("Latitude") !== -1).toBe(true); From eb781ae1d0a1a117a400faa92af43924591ef9e2 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Wed, 22 Mar 2023 15:30:44 -0400 Subject: [PATCH 08/18] Fix 3D Tiles clipping planes Sandcastle --- .../gallery/3D Tiles Clipping Planes.html | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html index b6ce63560bf0..7df0fc46db18 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html +++ b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html @@ -142,8 +142,9 @@ } let tileset; - async function loadTileset(resource) { + async function loadTileset(resource, modelMatrix) { const currentExampleType = viewModel.currentExampleType; + clippingPlanes = new Cesium.ClippingPlaneCollection({ planes: [ new Cesium.ClippingPlane( @@ -159,11 +160,16 @@ tileset = await Cesium.Cesium3DTileset.fromUrl(url, { clippingPlanes: clippingPlanes, }); + if (currentExampleType !== viewModel.currentExampleType) { // Another tileset was loaded, discard the current result return; } + if (Cesium.defined(modelMatrix)) { + tileset.modelMatrix = modelMatrix; + } + viewer.scene.primitives.add(tileset); tileset.debugShowBoundingVolume = @@ -308,12 +314,13 @@ } else if (newValue === clipObjects[1]) { loadTileset(pointCloudUrl); } else if (newValue === clipObjects[2]) { - loadTileset(instancedUrl).then(() => { - // Position the instanced tileset above terrain - tileset.modelMatrix = new Cesium.Matrix4.fromTranslation( + // Position the instanced tileset above terrain + loadTileset( + instancedUrl, + Cesium.Matrix4.fromTranslation( new Cesium.Cartesian3(15.0, -58.6, 50.825) - ); - }); + ) + ); } else { loadModel(modelUrl); } From 64f2d61be70e8ee1860c2864426175bbdd39bc5c Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Wed, 22 Mar 2023 16:39:11 -0400 Subject: [PATCH 09/18] Fix Sandcastle, docs --- Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html | 2 +- packages/engine/Source/Scene/Cesium3DTileset.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html b/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html index c69b695c55ba..6423a6837fcf 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html +++ b/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html @@ -385,7 +385,7 @@ try { // 3D Tiles Next converted from CDB of Aden, Yemen (CDB provided by Presagis) const terrainTileset = await Cesium.Cesium3DTileset.fromIonAssetId( - 1240402 + 667809 ); viewer.scene.primitives.add(terrainTileset); const buildingsTileset = await Cesium.Cesium3DTileset.fromIonAssetId( diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index f01a49263e63..42dfd843cb17 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -1957,11 +1957,14 @@ Object.defineProperties(Cesium3DTileset.prototype, { * * @param {number} assetId The Cesium ion asset id. * @param {Cesium3DTileset.ConstructorOptions} options An object describing initialization options + * @returns {Promise} * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * + * @see Cesium3DTileset#fromUrl + * * @example - * //Load a Cesium3DTileset with asset ID of 124624234 + * // Load a Cesium3DTileset with a Cesium ion asset ID of 124624234 * try { * const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(124624234); * scene.primitives.add(tileset); @@ -1980,9 +1983,12 @@ Cesium3DTileset.fromIonAssetId = async function (assetId, options) { * * @param {Resource|string} url The url to a tileset JSON file. * @param {Cesium3DTileset.ConstructorOptions} options An object describing initialization options + * @returns {Promise} * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * + * @see Cesium3DTileset#fromIonAssetId + * * @example * try { * const tileset = await Cesium.Cesium3DTileset.fromUrl( From bc8ee55c40a8e969dece438454006c122d965b0d Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 22 Mar 2023 17:45:43 -0400 Subject: [PATCH 10/18] Fix typos in READMEs --- .../Contributors/PerformanceTestingGuide/README.md | 7 +++++-- Documentation/Contributors/TestingGuide/README.md | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Documentation/Contributors/PerformanceTestingGuide/README.md b/Documentation/Contributors/PerformanceTestingGuide/README.md index decea410bfee..ff4a98e9f111 100644 --- a/Documentation/Contributors/PerformanceTestingGuide/README.md +++ b/Documentation/Contributors/PerformanceTestingGuide/README.md @@ -127,7 +127,8 @@ class Timer { To time how long it takes for the tileset.json to be fetched and the `Cesium3DTileset` to be initialized (not including the tile content), start -the timer just before creating the tileset with `Cesium3DTileset.fromUrl`, and stop it after the asynchronous function has completed. +the timer just before creating the tileset with `Cesium3DTileset.fromUrl`, and +stop it after the asynchronous function has completed. ```js tilesetTimer.start(); @@ -139,7 +140,9 @@ tilesetTimer.print(); ### How to measure tile load time To time how long it takes for all the tiles in the current camera view to load -(not including the tileset load time), after the tileset has been created with `Cesium3DTileset.fromUrl` and stop it in the `initialTilesLoaded` event handler. This event is used +(not including the tileset load time), start the timer after the tileset has +been created with `Cesium3DTileset.fromUrl` and stop it in the `initialTilesLoaded` +event handler. This event is used because we only care about our initial fixed camera view. `allTilesLoaded`, in contrast, may trigger multiple times if the camera moves, which is undesireable here. diff --git a/Documentation/Contributors/TestingGuide/README.md b/Documentation/Contributors/TestingGuide/README.md index 6b367b69ebdc..b8a315f5bb38 100644 --- a/Documentation/Contributors/TestingGuide/README.md +++ b/Documentation/Contributors/TestingGuide/README.md @@ -714,7 +714,7 @@ it("renders glTF model", async function () { }); ``` -Given a model's url and other options, [`loadAndZoomToModelAsync`](https://github.com/CesiumGS/cesium/blob/main/paclages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js) loads a model, configures the camera, and returns a promise that resolves when a model is ready for rendering. +Given a model's url and other options, [`loadAndZoomToModelAsync`](https://github.com/CesiumGS/cesium/blob/main/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js) loads a model, configures the camera, and returns a promise that resolves when a model is ready for rendering. Since loading a model requires asynchronous requests and creating WebGL resources that may be spread over several frames, CesiumJS's [`pollToPromise`](https://github.com/CesiumGS/cesium/blob/main/Specs/pollToPromise.js) is used to return a promise that resolves when the model is ready, which occurs by rendering the scene in an implicit loop (hence the name "poll") until `model.ready` is `true` or the `timeout` is reached. @@ -737,7 +737,7 @@ it("can create a billboard using a URL", async function () { Here a billboard is loaded using a url to image. Internally, `Billboard` makes an asynchronous request for the image and then sets its `ready` property to `true`. The function passed to `pollToPromise` just returns the value of `ready`; it does not need to render the scene to progressively complete the request like `Model`. Finally, the test verifies that the billboard is green. -To test if a promises rejects, we use `expectAsync` and provide the expected error type and message. Here is an excerpt from [ArcGISTiledElevationTerrainProviderSpec.js](https://github.com/CesiumGS/cesium/blob/main/packages/engine/Specs/Core/ArcGISTiledElevationTerrainProviderSpec.js): +To test if a promise rejects, we use `expectAsync` and provide the expected error type and message. Here is an excerpt from [ArcGISTiledElevationTerrainProviderSpec.js](https://github.com/CesiumGS/cesium/blob/main/packages/engine/Specs/Core/ArcGISTiledElevationTerrainProviderSpec.js): ```javascript it("fromUrl throws if the SRS is not supported", async function () { From 5f94afc5255597b4ef9896b7f9ecbebc821d34d1 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Thu, 23 Mar 2023 14:34:52 -0400 Subject: [PATCH 11/18] Small fixes --- Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html | 3 --- packages/engine/Source/Scene/Cesium3DTileContentFactory.js | 1 - packages/engine/Source/Scene/Vector3DTileGeometry.js | 2 +- packages/engine/Source/Scene/Vector3DTilePoints.js | 1 - packages/engine/Source/Scene/Vector3DTilePolygons.js | 4 +--- packages/engine/Source/Scene/Vector3DTilePrimitive.js | 1 - 6 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html b/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html index 6423a6837fcf..1c17a7059da7 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html +++ b/Apps/Sandcastle/gallery/3D Tiles Next CDB Yemen.html @@ -400,9 +400,6 @@ 12.753525, 2000 ); - const heading = Cesium.Math.toRadians(50.0); - const pitch = Cesium.Math.toRadians(-20.0); - const range = 5000.0; flyCameraTo(cameraTransforms.tileset); diff --git a/packages/engine/Source/Scene/Cesium3DTileContentFactory.js b/packages/engine/Source/Scene/Cesium3DTileContentFactory.js index 6e2186c31ca4..281235e37778 100644 --- a/packages/engine/Source/Scene/Cesium3DTileContentFactory.js +++ b/packages/engine/Source/Scene/Cesium3DTileContentFactory.js @@ -67,7 +67,6 @@ const Cesium3DTileContentFactory = { tileset, tile, resource, - arrayBuffer, byteOffset ); diff --git a/packages/engine/Source/Scene/Vector3DTileGeometry.js b/packages/engine/Source/Scene/Vector3DTileGeometry.js index 9dd4c3e71389..39ce487b1fc1 100644 --- a/packages/engine/Source/Scene/Vector3DTileGeometry.js +++ b/packages/engine/Source/Scene/Vector3DTileGeometry.js @@ -340,7 +340,7 @@ function createPrimitive(geometries) { return; } - this._error = error; + geometries._error = error; }); } } diff --git a/packages/engine/Source/Scene/Vector3DTilePoints.js b/packages/engine/Source/Scene/Vector3DTilePoints.js index 76e151edc6f6..42c65633d7f9 100644 --- a/packages/engine/Source/Scene/Vector3DTilePoints.js +++ b/packages/engine/Source/Scene/Vector3DTilePoints.js @@ -52,7 +52,6 @@ function Vector3DTilePoints(options) { this._polylineCollection = new PolylineCollection(); this._polylineCollection._useHighlightColor = true; - this._verticesPromise = undefined; this._packedBuffer = undefined; this._ready = false; diff --git a/packages/engine/Source/Scene/Vector3DTilePolygons.js b/packages/engine/Source/Scene/Vector3DTilePolygons.js index a5a37be0fc7e..dfc26848fe2b 100644 --- a/packages/engine/Source/Scene/Vector3DTilePolygons.js +++ b/packages/engine/Source/Scene/Vector3DTilePolygons.js @@ -40,8 +40,7 @@ import Vector3DTilePrimitive from "./Vector3DTilePrimitive.js"; * @private */ function Vector3DTilePolygons(options) { - // All of the private properties will be released except _readyPromise - // and _primitive after the Vector3DTilePrimitive is created. + // All of the private properties will be released except _primitive after the Vector3DTilePrimitive is created. this._batchTable = options.batchTable; this._batchIds = options.batchIds; @@ -377,7 +376,6 @@ function finishPrimitive(polygons) { polygons._boundingVolume = undefined; polygons._boundingVolumes = undefined; polygons._batchedIndices = undefined; - polygons._verticesPromise = undefined; } } diff --git a/packages/engine/Source/Scene/Vector3DTilePrimitive.js b/packages/engine/Source/Scene/Vector3DTilePrimitive.js index 5ae75ea11d29..0f16df4a98fb 100644 --- a/packages/engine/Source/Scene/Vector3DTilePrimitive.js +++ b/packages/engine/Source/Scene/Vector3DTilePrimitive.js @@ -249,7 +249,6 @@ function createVertexArray(primitive, context) { primitive._batchedPositions = undefined; primitive._transferrableBatchIds = undefined; primitive._vertexBatchIds = undefined; - primitive._verticesPromise = undefined; } function createShaders(primitive, context) { From 58e93731f4746af0b7282e5565cfebbee965a325 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Thu, 23 Mar 2023 14:45:02 -0400 Subject: [PATCH 12/18] Add specs --- packages/engine/Source/Scene/Cesium3DTileset.js | 4 ++++ packages/engine/Specs/Scene/Cesium3DTilesetSpec.js | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 42dfd843cb17..9f5526d1de77 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -1973,6 +1973,10 @@ Object.defineProperties(Cesium3DTileset.prototype, { * } */ Cesium3DTileset.fromIonAssetId = async function (assetId, options) { + //>>includeStart('debug', pragmas.debug); + Check.defined("assetId", assetId); + //>>includeEnd('debug'); + const resource = await IonResource.fromAssetId(assetId); return Cesium3DTileset.fromUrl(resource, options); }; diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js index 92902d24dcff..e93bdc4c7f20 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js @@ -470,6 +470,14 @@ describe( expect(tileset.resource.url).toEqual(path + param); }); + it("fromIonAssetId throws without assetId", async function () { + await expectAsync( + Cesium3DTileset.fromIonAssetId() + ).toBeRejectedWithDeveloperError( + "assetId is required, actual value was undefined" + ); + }); + it("resolves readyPromise", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset From b5a82a4a16bc5aea4307e6d655e9a13ca7614390 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Mon, 27 Mar 2023 13:45:17 -0400 Subject: [PATCH 13/18] Update TimeDynamicPointCloud, I3S, and Voxels readyPromise --- .../gallery/I3S 3D Object Layer.html | 66 ++-- .../gallery/I3S Feature Picking.html | 66 ++-- .../gallery/I3S IntegratedMesh Layer.html | 65 ++-- .../gallery/development/Frustum.html | 10 +- .../gallery/development/Voxels.html | 42 +-- CHANGES.md | 5 + packages/engine/Source/Scene/Cesium3DTile.js | 2 +- .../Scene/Cesium3DTilesVoxelProvider.js | 281 ++++++++++------ .../engine/Source/Scene/Cesium3DTileset.js | 8 +- .../Source/Scene/ClassificationPrimitive.js | 7 + .../Source/Scene/GroundPolylinePrimitive.js | 13 +- .../engine/Source/Scene/GroundPrimitive.js | 9 +- .../engine/Source/Scene/I3SDataProvider.js | 316 ++++++++++++------ packages/engine/Source/Scene/I3SFeature.js | 13 +- packages/engine/Source/Scene/I3SLayer.js | 2 +- packages/engine/Source/Scene/I3SNode.js | 125 ++++--- packages/engine/Source/Scene/PointCloud.js | 30 +- packages/engine/Source/Scene/Primitive.js | 20 ++ .../Source/Scene/TimeDynamicPointCloud.js | 17 +- packages/engine/Source/Scene/VoxelContent.js | 124 ++++--- .../engine/Source/Scene/VoxelPrimitive.js | 130 +++---- packages/engine/Source/Scene/VoxelProvider.js | 4 +- .../Scene/Cesium3DTilesVoxelProviderSpec.js | 86 +++-- .../engine/Specs/Scene/I3SDataProviderSpec.js | 115 ++----- packages/engine/Specs/Scene/I3SLayerSpec.js | 4 +- packages/engine/Specs/Scene/I3SNodeSpec.js | 63 ++-- .../Specs/Scene/TimeDynamicPointCloudSpec.js | 22 +- .../engine/Specs/Scene/VoxelPrimitiveSpec.js | 32 +- .../Specs/Scene/buildVoxelDrawCommandsSpec.js | 10 +- .../Specs/Scene/processVoxelPropertiesSpec.js | 12 +- packages/widgets/Source/Viewer/Viewer.js | 5 +- .../VoxelInspector/VoxelInspectorViewModel.js | 3 +- packages/widgets/Specs/Viewer/ViewerSpec.js | 10 +- 33 files changed, 931 insertions(+), 786 deletions(-) diff --git a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html index 778356edb8e3..20de32645afd 100644 --- a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html +++ b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html @@ -50,38 +50,46 @@

    Loading...

    "San Francisco": "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/SanFrancisco_3DObjects_1_7/SceneServer/layers/0", }; - // Initialize a terrain provider which provides geoid conversion between gravity related (typically I3S datasets) and ellipsoidal based - // height systems (Cesium World Terrain). - // If this is not specified, or the URL is invalid no geoid conversion will be applied. - // The source data used in this transcoding service was compiled from https://earth-info.nga.mil/#tab_wgs84-data and is based on EGM2008 Gravity Model - const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl( - "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer" - ); - // Create i3s and Cesium3DTileset options to pass optional parameters useful for debugging and visualizing - const cesium3dTilesetOptions = { - skipLevelOfDetail: false, - debugShowBoundingVolume: false, - }; - const i3sOptions = { - url: tours["San Francisco"], - traceFetches: false, // for tracing I3S fetches - geoidTiledTerrainProvider: geoidService, // pass the geoid service - cesium3dTilesetOptions: cesium3dTilesetOptions, // options for internal Cesium3dTileset - }; - // Create I3S data provider - const i3sProvider = new Cesium.I3SDataProvider(i3sOptions); + try { + // Initialize a terrain provider which provides geoid conversion between gravity related (typically I3S datasets) and ellipsoidal based + // height systems (Cesium World Terrain). + // If this is not specified, or the URL is invalid no geoid conversion will be applied. + // The source data used in this transcoding service was compiled from https://earth-info.nga.mil/#tab_wgs84-data and is based on EGM2008 Gravity Model + const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl( + "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer" + ); + // Create i3s and Cesium3DTileset options to pass optional parameters useful for debugging and visualizing + const cesium3dTilesetOptions = { + skipLevelOfDetail: false, + debugShowBoundingVolume: false, + }; + const i3sOptions = { + traceFetches: false, // for tracing I3S fetches + geoidTiledTerrainProvider: geoidService, // pass the geoid service + cesium3dTilesetOptions: cesium3dTilesetOptions, // options for internal Cesium3dTileset + }; - // Add the i3s layer provider as a primitive data type - viewer.scene.primitives.add(i3sProvider); + // Create I3S data provider + const i3sProvider = await Cesium.I3SDataProvider.fromUrl( + tours["San Francisco"], + i3sOptions + ); - // Center camera on I3S once it's loaded - await i3sProvider.readyPromise; - const center = Cesium.Rectangle.center(i3sProvider.extent); - center.height = 10000.0; - viewer.camera.setView({ - destination: Cesium.Ellipsoid.WGS84.cartographicToCartesian(center), - }); + // Add the i3s layer provider as a primitive data type + viewer.scene.primitives.add(i3sProvider); + + // Center camera on I3S once it's loaded + const center = Cesium.Rectangle.center(i3sProvider.extent); + center.height = 10000.0; + viewer.camera.setView({ + destination: Cesium.Ellipsoid.WGS84.cartographicToCartesian(center), + }); + } catch (error) { + console.log( + `There was an error creating the I3S Data Provider: ${error}` + ); + } // An entity object which will hold info about the currently selected feature for infobox display const selectedEntity = new Cesium.Entity(); diff --git a/Apps/Sandcastle/gallery/I3S Feature Picking.html b/Apps/Sandcastle/gallery/I3S Feature Picking.html index c8c8348b4153..692608b9e5ec 100644 --- a/Apps/Sandcastle/gallery/I3S Feature Picking.html +++ b/Apps/Sandcastle/gallery/I3S Feature Picking.html @@ -50,38 +50,46 @@

    Loading...

    "New York": "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/NYC_Attributed_v17/SceneServer", }; - // Initialize a terrain provider which provides geoid conversion between gravity related (typically I3S datasets) and ellipsoidal based - // height systems (Cesium World Terrain). - // If this is not specified, or the URL is invalid no geoid conversion will be applied. - // The source data used in this transcoding service was compiled from https://earth-info.nga.mil/#tab_wgs84-data and is based on EGM2008 Gravity Model - const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl( - "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer" - ); - // Create i3s and Cesium3DTileset options to pass optional parameters useful for debugging and visualizing - const cesium3dTilesetOptions = { - skipLevelOfDetail: false, - debugShowBoundingVolume: false, - }; - const i3sOptions = { - url: tours["New York"], - traceFetches: false, // for tracing I3S fetches - geoidTiledTerrainProvider: geoidService, // pass the geoid service - cesium3dTilesetOptions: cesium3dTilesetOptions, // options for internal Cesium3dTileset - }; - // Create I3S data provider - const i3sProvider = new Cesium.I3SDataProvider(i3sOptions); + try { + // Initialize a terrain provider which provides geoid conversion between gravity related (typically I3S datasets) and ellipsoidal based + // height systems (Cesium World Terrain). + // If this is not specified, or the URL is invalid no geoid conversion will be applied. + // The source data used in this transcoding service was compiled from https://earth-info.nga.mil/#tab_wgs84-data and is based on EGM2008 Gravity Model + const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl( + "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer" + ); + // Create i3s and Cesium3DTileset options to pass optional parameters useful for debugging and visualizing + const cesium3dTilesetOptions = { + skipLevelOfDetail: false, + debugShowBoundingVolume: false, + }; + const i3sOptions = { + traceFetches: false, // for tracing I3S fetches + geoidTiledTerrainProvider: geoidService, // pass the geoid service + cesium3dTilesetOptions: cesium3dTilesetOptions, // options for internal Cesium3dTileset + }; - // Add the i3s layer provider as a primitive data type - viewer.scene.primitives.add(i3sProvider); + // Create I3S data provider + const i3sProvider = await Cesium.I3SDataProvider.fromUrl( + tours["New York"], + i3sOptions + ); - // Center camera on I3S once it's loaded - await i3sProvider.readyPromise; - const center = Cesium.Rectangle.center(i3sProvider.extent); - center.height = 10000.0; - viewer.camera.setView({ - destination: Cesium.Ellipsoid.WGS84.cartographicToCartesian(center), - }); + // Add the i3s layer provider as a primitive data type + viewer.scene.primitives.add(i3sProvider); + + // Center camera on I3S once it's loaded + const center = Cesium.Rectangle.center(i3sProvider.extent); + center.height = 10000.0; + viewer.camera.setView({ + destination: Cesium.Ellipsoid.WGS84.cartographicToCartesian(center), + }); + } catch (error) { + console.log( + `There was an error creating the I3S Data Provider: ${error}` + ); + } // An entity object which will hold info about the currently selected feature for infobox display const selectedEntity = new Cesium.Entity(); diff --git a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html index 935b2fc0dd9e..50fcdbc32c9d 100644 --- a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html +++ b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html @@ -52,39 +52,46 @@

    Loading...

    Frankfurt: "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer/layers/0", }; - // Initialize a terrain provider which provides geoid conversion between gravity related (typically I3S datasets) and ellipsoidal based - // height systems (Cesium World Terrain). - // If this is not specified, or the URL is invalid no geoid conversion will be applied. - // The source data used in this transcoding service was compiled from https://earth-info.nga.mil/#tab_wgs84-data and is based on EGM2008 Gravity Model - const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl( - "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer" - ); + try { + // Initialize a terrain provider which provides geoid conversion between gravity related (typically I3S datasets) and ellipsoidal based + // height systems (Cesium World Terrain). + // If this is not specified, or the URL is invalid no geoid conversion will be applied. + // The source data used in this transcoding service was compiled from https://earth-info.nga.mil/#tab_wgs84-data and is based on EGM2008 Gravity Model + const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl( + "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer" + ); - // Create i3s and Cesium3DTileset options to pass optional parameters useful for debugging and visualizing - const cesium3dTilesetOptions = { - skipLevelOfDetail: false, - debugShowBoundingVolume: false, - }; - const i3sOptions = { - url: tours.Frankfurt, - traceFetches: false, // for tracing I3S fetches - geoidTiledTerrainProvider: geoidService, // pass the geoid service - cesium3dTilesetOptions: cesium3dTilesetOptions, // options for internal Cesium3dTileset - }; + // Create i3s and Cesium3DTileset options to pass optional parameters useful for debugging and visualizing + const cesium3dTilesetOptions = { + skipLevelOfDetail: false, + debugShowBoundingVolume: false, + }; + const i3sOptions = { + traceFetches: false, // for tracing I3S fetches + geoidTiledTerrainProvider: geoidService, // pass the geoid service + cesium3dTilesetOptions: cesium3dTilesetOptions, // options for internal Cesium3dTileset + }; - // Create I3S data provider - const i3sProvider = new Cesium.I3SDataProvider(i3sOptions); + // Create I3S data provider + const i3sProvider = await Cesium.I3SDataProvider.fromUrl( + tours.Frankfurt, + i3sOptions + ); - // Add the i3s layer provider as a primitive data type - viewer.scene.primitives.add(i3sProvider); + // Add the i3s layer provider as a primitive data type + viewer.scene.primitives.add(i3sProvider); - // Center camera on I3S once it's loaded - await i3sProvider.readyPromise; - const center = Cesium.Rectangle.center(i3sProvider.extent); - center.height = 10000.0; - viewer.camera.setView({ - destination: Cesium.Ellipsoid.WGS84.cartographicToCartesian(center), - }); //Sandcastle_End + // Center camera on I3S once it's loaded + const center = Cesium.Rectangle.center(i3sProvider.extent); + center.height = 10000.0; + viewer.camera.setView({ + destination: Cesium.Ellipsoid.WGS84.cartographicToCartesian(center), + }); + } catch (error) { + console.log( + `There was an error creating the I3S Data Provider: ${error}` + ); + } //Sandcastle_End }; if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Apps/Sandcastle/gallery/development/Frustum.html b/Apps/Sandcastle/gallery/development/Frustum.html index ce5e4eee3ac7..82ea8dc2d3a5 100644 --- a/Apps/Sandcastle/gallery/development/Frustum.html +++ b/Apps/Sandcastle/gallery/development/Frustum.html @@ -106,11 +106,17 @@ }) ); - frustumPrimitive.readyPromise.then(function (primitive) { - const bs = primitive.getGeometryInstanceAttributes("frustum") + const removeListener = scene.postRender.addEventListener(() => { + if (!frustumPrimitive.ready) { + return; + } + + const bs = frustumPrimitive.getGeometryInstanceAttributes("frustum") .boundingSphere; scene.camera.viewBoundingSphere(bs); scene.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); + + removeListener(); }); //Sandcastle_End }; diff --git a/Apps/Sandcastle/gallery/development/Voxels.html b/Apps/Sandcastle/gallery/development/Voxels.html index a4d414c95c89..7280815f412c 100644 --- a/Apps/Sandcastle/gallery/development/Voxels.html +++ b/Apps/Sandcastle/gallery/development/Voxels.html @@ -78,7 +78,6 @@ this.types = [Cesium.MetadataType.VEC4]; this.componentTypes = [Cesium.MetadataComponentType.FLOAT32]; this.ready = true; - this.readyPromise = Promise.resolve(this); } const scratchColor = new Cesium.Color(); @@ -301,16 +300,10 @@ }) ); - voxelPrimitive.readyPromise - .then(function () { - viewer.voxelInspector.viewModel.voxelPrimitive = voxelPrimitive; - viewer.camera.flyToBoundingSphere(voxelPrimitive.boundingSphere, { - duration: 0.0, - }); - }) - .catch(function (error) { - console.log(error); - }); + viewer.voxelInspector.viewModel.voxelPrimitive = voxelPrimitive; + viewer.camera.flyToBoundingSphere(voxelPrimitive.boundingSphere, { + duration: 0.0, + }); return voxelPrimitive; } @@ -394,11 +387,10 @@ }, { text: "Box - 3D Tiles", - onselect: function () { - const provider = new Cesium.Cesium3DTilesVoxelProvider({ - url: - "../../SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json", - }); + onselect: async function () { + const provider = await Cesium.Cesium3DTilesVoxelProvider.fromUrl( + "../../SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json" + ); const primitive = createPrimitive( provider, customShaderWhite, @@ -421,11 +413,10 @@ }, { text: "Ellipsoid - 3D Tiles", - onselect: function () { - const provider = new Cesium.Cesium3DTilesVoxelProvider({ - url: - "../../SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json", - }); + onselect: async function () { + const provider = await Cesium.Cesium3DTilesVoxelProvider.fromUrl( + "../../SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json" + ); const primitive = createPrimitive( provider, customShaderWhite, @@ -448,11 +439,10 @@ }, { text: "Cylinder - 3D Tiles", - onselect: function () { - const provider = new Cesium.Cesium3DTilesVoxelProvider({ - url: - "../../SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json", - }); + onselect: async function () { + const provider = await Cesium.Cesium3DTilesVoxelProvider.fromUrl( + "../../SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json" + ); const primitive = createPrimitive( provider, customShaderWhite, diff --git a/CHANGES.md b/CHANGES.md index 6b282ba90bf8..003796f07eb8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -74,6 +74,11 @@ try { console.log(`Failed to load model. ${error}`); } ``` +- `I3SDataProvider` construction parameter `options.url`, `I3SDataProvider.ready`, and `I3SDataProvider.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `I3SDataProvider.fromUrl` instead. +- `TimeDynamicPointCloud.readyPromise` was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use `TimeDynamicPointCloud.frameFailed` to track any errors. +- `VoxelProvider.ready` and `VoxelProvider.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. +- `Cesium3DTilesVoxelProvider` construction parameter `options.url`, `Cesium3DTilesVoxelProvider.ready`, and `Cesium3DTilesVoxelProvider.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `Cesium3DTilesVoxelProvider.fromUrl` instead. +- `Primitive.readyPromise`, `ClassificationPrimitive.readyPromise`, `GroundPrimitive.readyPromise`, and `GroundPolylinePrimitive.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Wait for `Primitive.ready`, `ClassificationPrimitive.ready`, `GroundPrimitive.ready`, or `GroundPolylinePrimitive.ready` to return true instead. #### @cesium/widgets diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js index de95fdc92b4d..30087fff82ad 100644 --- a/packages/engine/Source/Scene/Cesium3DTile.js +++ b/packages/engine/Source/Scene/Cesium3DTile.js @@ -1046,7 +1046,7 @@ function createPriorityFunction(tile) { * The request may not be made if the Cesium Request Scheduler can't prioritize it. *

    * - * @return {Promise|undefined} A promise that resolves when the request completes, or undefined if there is no request needed, or the request cannot be scheduled. + * @return {Promise|undefined} A promise that resolves when the request completes, or undefined if there is no request needed, or the request cannot be scheduled. * @private */ Cesium3DTile.prototype.requestContent = function () { diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index 02e1904a612e..5a84f8f9d99c 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -2,7 +2,7 @@ import Cartesian3 from "../Core/Cartesian3.js"; import Check from "../Core/Check.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; -import DeveloperError from "../Core/DeveloperError.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import Ellipsoid from "../Core/Ellipsoid.js"; import Matrix4 from "../Core/Matrix4.js"; import OrientedBoundingBox from "../Core/OrientedBoundingBox.js"; @@ -28,14 +28,18 @@ import VoxelShapeType from "./VoxelShapeType.js"; *

    * Implements the {@link VoxelProvider} interface. *

    + *
    + * This object is normally not instantiated directly, use {@link Cesium3DTilesVoxelProvider.fromUrl}. + *
    * * @alias Cesium3DTilesVoxelProvider * @constructor * @augments VoxelProvider * * @param {object} options Object with the following properties: - * @param {Resource|string|Promise|Promise} options.url The URL to a tileset JSON file. + * @param {Resource|string|Promise|Promise} [options.url] The URL to a tileset JSON file. Deprecated. * + * @see Cesium3DTilesVoxelProvider.fromUrl * @see VoxelProvider * @see VoxelPrimitive * @see VoxelShapeType @@ -44,12 +48,7 @@ import VoxelShapeType from "./VoxelShapeType.js"; */ function Cesium3DTilesVoxelProvider(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - //>>includeStart('debug', pragmas.debug) - Check.defined("options.url", options.url); - //>>includeEnd('debug'); - - /** @inheritdoc */ - this.ready = false; + this._ready = false; /** @inheritdoc */ this.shapeTransform = undefined; @@ -99,84 +98,187 @@ function Cesium3DTilesVoxelProvider(options) { const that = this; let tilesetJson; - this._readyPromise = Promise.resolve(options.url).then(function (url) { - const resource = Resource.createIfNeeded(url); - return resource - .fetchJson() - .then(function (tileset) { - tilesetJson = tileset; - validate(tilesetJson); - const schemaLoader = getMetadataSchemaLoader(tilesetJson, resource); - return schemaLoader.load(); - }) - .then(function (schemaLoader) { - const root = tilesetJson.root; - const voxel = root.content.extensions["3DTILES_content_voxels"]; - const className = voxel.class; - - const metadataJson = hasExtension(tilesetJson, "3DTILES_metadata") - ? tilesetJson.extensions["3DTILES_metadata"] - : tilesetJson; - - const metadataSchema = schemaLoader.schema; - const metadata = new Cesium3DTilesetMetadata({ - metadataJson: metadataJson, - schema: metadataSchema, + if (defined(options.url)) { + deprecationWarning( + "Cesium3DTilesVoxelProvider options.url", + "Cesium3DTilesVoxelProvider constructor parameter options.url was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use Cesium3DTilesVoxelProvider.fromUrl instead." + ); + + this._readyPromise = Promise.resolve(options.url).then(function (url) { + const resource = Resource.createIfNeeded(url); + return resource + .fetchJson() + .then(function (tileset) { + tilesetJson = tileset; + validate(tilesetJson); + const schemaLoader = getMetadataSchemaLoader(tilesetJson, resource); + return schemaLoader.load(); + }) + .then(function (schemaLoader) { + const root = tilesetJson.root; + const voxel = root.content.extensions["3DTILES_content_voxels"]; + const className = voxel.class; + + const metadataJson = hasExtension(tilesetJson, "3DTILES_metadata") + ? tilesetJson.extensions["3DTILES_metadata"] + : tilesetJson; + + const metadataSchema = schemaLoader.schema; + const metadata = new Cesium3DTilesetMetadata({ + metadataJson: metadataJson, + schema: metadataSchema, + }); + + addAttributeInfo(that, metadata, className); + + const implicitTileset = new ImplicitTileset( + resource, + root, + metadataSchema + ); + + const { + shape, + minBounds, + maxBounds, + shapeTransform, + globalTransform, + } = getShape(root); + + that.shape = shape; + that.minBounds = minBounds; + that.maxBounds = maxBounds; + that.dimensions = Cartesian3.unpack(voxel.dimensions); + that.shapeTransform = shapeTransform; + that.globalTransform = globalTransform; + that.maximumTileCount = getTileCount(metadata); + + let paddingBefore; + let paddingAfter; + + if (defined(voxel.padding)) { + paddingBefore = Cartesian3.unpack(voxel.padding.before); + paddingAfter = Cartesian3.unpack(voxel.padding.after); + } + + that.paddingBefore = paddingBefore; + that.paddingAfter = paddingAfter; + + that._implicitTileset = implicitTileset; + + ResourceCache.unload(schemaLoader); + + that._ready = true; + return that; }); + }); + } +} - addAttributeInfo(that, metadata, className); +Object.defineProperties(Cesium3DTilesVoxelProvider.prototype, { + /** @inheritdoc */ + readyPromise: { + get: function () { + deprecationWarning( + "Cesium3DTilesVoxelProvider.readyPromise", + "Cesium3DTilesVoxelProvider.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use Cesium3DTilesVoxelProvider.fromUrl instead." + ); + return this._readyPromise; + }, + }, + /** @inheritdoc */ + ready: { + get: function () { + deprecationWarning( + "Cesium3DTilesVoxelProvider.ready", + "Cesium3DTilesVoxelProvider.ready was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use Cesium3DTilesVoxelProvider.fromUrl instead." + ); + return this._ready; + }, + }, +}); - const implicitTileset = new ImplicitTileset( - resource, - root, - metadataSchema - ); +/** + * Creates a {@link VoxelProvider} that fetches voxel data from a 3D Tiles tileset. + * + * @param {Resource|string} url The URL to a tileset JSON file + * @returns {Promise} The created provider + * + * @exception {RuntimeException} Root must have content + * @exception {RuntimeException} Root tile content must have 3DTILES_content_voxels extension + * @exception {RuntimeException} Root tile must have implicit tiling + * @exception {RuntimeException} Tileset must have a metadata schema + * @exception {RuntimeException} Only box, region and 3DTILES_bounding_volume_cylinder are supported in Cesium3DTilesVoxelProvider + */ +Cesium3DTilesVoxelProvider.fromUrl = async function (url) { + //>>includeStart('debug', pragmas.debug); + Check.defined("url", url); + //>>includeEnd('debug'); - const { - shape, - minBounds, - maxBounds, - shapeTransform, - globalTransform, - } = getShape(root); + const resource = Resource.createIfNeeded(url); + const tilesetJson = await resource.fetchJson(); - that.shape = shape; - that.minBounds = minBounds; - that.maxBounds = maxBounds; - that.dimensions = Cartesian3.unpack(voxel.dimensions); - that.shapeTransform = shapeTransform; - that.globalTransform = globalTransform; - that.maximumTileCount = getTileCount(metadata); + validate(tilesetJson); - let paddingBefore; - let paddingAfter; + const schemaLoader = getMetadataSchemaLoader(tilesetJson, resource); + await schemaLoader.load(); - if (defined(voxel.padding)) { - paddingBefore = Cartesian3.unpack(voxel.padding.before); - paddingAfter = Cartesian3.unpack(voxel.padding.after); - } + const root = tilesetJson.root; + const voxel = root.content.extensions["3DTILES_content_voxels"]; + const className = voxel.class; - that.paddingBefore = paddingBefore; - that.paddingAfter = paddingAfter; + const metadataJson = hasExtension(tilesetJson, "3DTILES_metadata") + ? tilesetJson.extensions["3DTILES_metadata"] + : tilesetJson; - that._implicitTileset = implicitTileset; + const metadataSchema = schemaLoader.schema; + const metadata = new Cesium3DTilesetMetadata({ + metadataJson: metadataJson, + schema: metadataSchema, + }); - ResourceCache.unload(schemaLoader); + const provider = new Cesium3DTilesVoxelProvider(); - that.ready = true; - return that; - }); - }); -} + addAttributeInfo(provider, metadata, className); -Object.defineProperties(Cesium3DTilesVoxelProvider.prototype, { - /** @inheritdoc */ - readyPromise: { - get: function () { - return this._readyPromise; - }, - }, -}); + const implicitTileset = new ImplicitTileset(resource, root, metadataSchema); + + const { + shape, + minBounds, + maxBounds, + shapeTransform, + globalTransform, + } = getShape(root); + + provider.shape = shape; + provider.minBounds = minBounds; + provider.maxBounds = maxBounds; + provider.dimensions = Cartesian3.unpack(voxel.dimensions); + provider.shapeTransform = shapeTransform; + provider.globalTransform = globalTransform; + provider.maximumTileCount = getTileCount(metadata); + + let paddingBefore; + let paddingAfter; + + if (defined(voxel.padding)) { + paddingBefore = Cartesian3.unpack(voxel.padding.before); + paddingAfter = Cartesian3.unpack(voxel.padding.after); + } + + provider.paddingBefore = paddingBefore; + provider.paddingAfter = paddingAfter; + + provider._implicitTileset = implicitTileset; + + ResourceCache.unload(schemaLoader); + + provider._ready = true; + provider._readyPromise = Promise.resolve(provider); + + return provider; +}; function getTileCount(metadata) { if (!defined(metadata.tileset)) { @@ -357,7 +459,7 @@ function copyArray(values, length) { return Array.from({ length }, (v, i) => valuesArray[i]); } -function getVoxelPromise(implicitTileset, tileCoordinates) { +async function getVoxelContent(implicitTileset, tileCoordinates) { const voxelRelative = implicitTileset.contentUriTemplates[0].getDerivedResource( { templateValues: tileCoordinates.getTemplateValues(), @@ -367,18 +469,17 @@ function getVoxelPromise(implicitTileset, tileCoordinates) { url: voxelRelative.url, }); - return voxelResource.fetchArrayBuffer().then(function (arrayBuffer) { - const preprocessed = preprocess3DTileContent(arrayBuffer); + const arrayBuffer = await voxelResource.fetchArrayBuffer(); + const preprocessed = preprocess3DTileContent(arrayBuffer); - const voxelContent = new VoxelContent( - voxelResource, - preprocessed.jsonPayload, - preprocessed.binaryPayload, - implicitTileset.metadataSchema - ); + const voxelContent = await VoxelContent.fromJson( + voxelResource, + preprocessed.jsonPayload, + preprocessed.binaryPayload, + implicitTileset.metadataSchema + ); - return voxelContent.readyPromise; - }); + return voxelContent; } async function getSubtreePromise(provider, subtreeCoord) { @@ -424,14 +525,6 @@ async function getSubtreePromise(provider, subtreeCoord) { /** @inheritdoc */ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) { - //>>includeStart('debug', pragmas.debug); - if (!this.ready) { - throw new DeveloperError( - "The provider is not ready. Use Cesium3DTilesVoxelProvider.readyPromise or wait for Cesium3DTilesVoxelProvider.ready to be true." - ); - } - //>>includeEnd('debug'); - options = defaultValue(options, defaultValue.EMPTY_OBJECT); const tileLevel = defaultValue(options.tileLevel, 0); const tileX = defaultValue(options.tileX, 0); @@ -482,7 +575,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) { return Promise.reject("Tile is not available"); } - return getVoxelPromise(implicitTileset, tileCoordinates); + return getVoxelContent(implicitTileset, tileCoordinates); }) .then(function (voxelContent) { return names.map(function (name) { diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 9f5526d1de77..0f34df03d51c 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -58,6 +58,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * * Initialization options for the Cesium3DTileset constructor * + * @property {Resource|string|Promise|Promise} [options.url] The url to a tileset JSON file. Deprecated. * @property {boolean} [options.show=true] Determines if the tileset will be shown. * @property {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the tileset's root tile. * @property {Axis} [options.modelUpAxis=Axis.Y] Which axis is considered up when loading models for tile contents. @@ -118,11 +119,6 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @property {boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. */ -/** - * @typedef {Cesium3DTileset.ConstructorOptions} Cesium3DTileset.DeprecatedConstructorOptions - * @property {Resource|string|Promise|Promise} options.url The url to a tileset JSON file. Deprecated. - */ - /** * A {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles tileset}, * used for streaming massive heterogeneous 3D geospatial datasets. @@ -134,7 +130,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @alias Cesium3DTileset * @constructor * - * @param {Cesium3DTileset.DeprecatedConstructorOptions} options An object describing initialization options + * @param {Cesium3DTileset.ConstructorOptions} options An object describing initialization options * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * diff --git a/packages/engine/Source/Scene/ClassificationPrimitive.js b/packages/engine/Source/Scene/ClassificationPrimitive.js index b9d1c608bce7..b0817890c2f6 100644 --- a/packages/engine/Source/Scene/ClassificationPrimitive.js +++ b/packages/engine/Source/Scene/ClassificationPrimitive.js @@ -2,6 +2,7 @@ import ColorGeometryInstanceAttribute from "../Core/ColorGeometryInstanceAttribu import combine from "../Core/combine.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import GeometryInstance from "../Core/GeometryInstance.js"; @@ -164,6 +165,7 @@ function ClassificationPrimitive(options) { this._ready = false; const classificationPrimitive = this; + // This is here for backwards compatibility. This promise wrapper can be removed once readyPromise is removed. this._readyPromise = new Promise((resolve, reject) => { classificationPrimitive._completeLoad = () => { if (this._ready) { @@ -340,9 +342,14 @@ Object.defineProperties(ClassificationPrimitive.prototype, { * @memberof ClassificationPrimitive.prototype * @type {Promise} * @readonly + * @deprecated */ readyPromise: { get: function () { + deprecationWarning( + "ClassificationPrimitive.readyPromise", + "ClassificationPrimitive.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for ClassificationPrimitive.ready to return true instead." + ); return this._readyPromise; }, }, diff --git a/packages/engine/Source/Scene/GroundPolylinePrimitive.js b/packages/engine/Source/Scene/GroundPolylinePrimitive.js index 77afcbe5b629..648329cac347 100644 --- a/packages/engine/Source/Scene/GroundPolylinePrimitive.js +++ b/packages/engine/Source/Scene/GroundPolylinePrimitive.js @@ -4,6 +4,7 @@ import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import GeometryInstance from "../Core/GeometryInstance.js"; import GeometryInstanceAttribute from "../Core/GeometryInstanceAttribute.js"; import GroundPolylineGeometry from "../Core/GroundPolylineGeometry.js"; @@ -193,6 +194,7 @@ function GroundPolylinePrimitive(options) { this._ready = false; const groundPolylinePrimitive = this; + // This is here for backwards compatibility. This promise wrapper can be removed once readyPromise is removed. this._readyPromise = new Promise((resolve, reject) => { groundPolylinePrimitive._completeLoad = () => { this._ready = true; @@ -318,9 +320,14 @@ Object.defineProperties(GroundPolylinePrimitive.prototype, { * @memberof GroundPolylinePrimitive.prototype * @type {Promise} * @readonly + * @deprecated */ readyPromise: { get: function () { + deprecationWarning( + "GroundPolylinePrimitive.readyPromise", + "GroundPolylinePrimitive.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for GroundPolylinePrimitive.ready to return true instead." + ); return this._readyPromise; }, }, @@ -825,7 +832,6 @@ GroundPolylinePrimitive.prototype.update = function (frameState) { }; this._primitive = new Primitive(primitiveOptions); - this._primitive.readyPromise.then(this._completeLoad); } if ( @@ -841,6 +847,11 @@ GroundPolylinePrimitive.prototype.update = function (frameState) { this._primitive.show = this.show; this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; this._primitive.update(frameState); + frameState.afterRender.push(() => { + if (!this._ready && defined(this._primitive) && this._primitive.ready) { + this._completeLoad(); + } + }); }; /** diff --git a/packages/engine/Source/Scene/GroundPrimitive.js b/packages/engine/Source/Scene/GroundPrimitive.js index 861bd2b44352..ce39516f3036 100644 --- a/packages/engine/Source/Scene/GroundPrimitive.js +++ b/packages/engine/Source/Scene/GroundPrimitive.js @@ -5,6 +5,7 @@ import Cartographic from "../Core/Cartographic.js"; import Check from "../Core/Check.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import GeometryInstance from "../Core/GeometryInstance.js"; @@ -213,6 +214,7 @@ function GroundPrimitive(options) { this._ready = false; const groundPrimitive = this; + // This is here for backwards compatibility. This promise wrapper can be removed once readyPromise is removed. this._readyPromise = new Promise((resolve, reject) => { groundPrimitive._completeLoad = () => { if (this._ready) { @@ -388,9 +390,14 @@ Object.defineProperties(GroundPrimitive.prototype, { * @memberof GroundPrimitive.prototype * @type {Promise} * @readonly + * @deprecated */ readyPromise: { get: function () { + deprecationWarning( + "GroundPrimitive.readyPromise", + "GroundPrimitive.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for GroundPrimitive.ready to return true instead." + ); return this._readyPromise; }, }, @@ -934,7 +941,7 @@ GroundPrimitive.prototype.update = function (frameState) { this._primitive.update(frameState); frameState.afterRender.push(() => { - if (defined(this._primitive) && this._primitive.ready) { + if (!this._ready && defined(this._primitive) && this._primitive.ready) { this._completeLoad(); } }); diff --git a/packages/engine/Source/Scene/I3SDataProvider.js b/packages/engine/Source/Scene/I3SDataProvider.js index f5b5dddef961..53e55f94a23c 100644 --- a/packages/engine/Source/Scene/I3SDataProvider.js +++ b/packages/engine/Source/Scene/I3SDataProvider.js @@ -51,59 +51,77 @@ import Cartographic from "../Core/Cartographic.js"; import Check from "../Core/Check.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; -import DeveloperError from "../Core/DeveloperError.js"; import HeightmapEncoding from "../Core/HeightmapEncoding.js"; import Resource from "../Core/Resource.js"; +import RuntimeError from "../Core/RuntimeError.js"; import TaskProcessor from "../Core/TaskProcessor.js"; import WebMercatorProjection from "../Core/WebMercatorProjection.js"; import I3SLayer from "./I3SLayer.js"; import Lerc from "lerc"; import Rectangle from "../Core/Rectangle.js"; +/** + * @typedef {Object} I3SDataProvider.ConstructorOptions + * + * Initialization options for the I3SDataProvider constructor + * + * @property {Resource|string} [options.url] The url of the I3S dataset. Deprecated. + * @property {string} [options.name] The name of the I3S dataset. + * @property {boolean} [options.show=true] Determines if the dataset will be shown. + * @property {ArcGISTiledElevationTerrainProvider|Promise} [options.geoidTiledTerrainProvider] Tiled elevation provider describing an Earth Gravitational Model. If defined, geometry will be shifted based on the offsets given by this provider. Required to position I3S data sets with gravity-related height at the correct location. + * @property {boolean} [options.traceFetches=false] Debug option. When true, log a message whenever an I3S tile is fetched. + * @property {Cesium3DTileset.ConstructorOptions} [options.cesium3dTilesetOptions] Object containing options to pass to an internally created {@link Cesium3DTileset}. See {@link Cesium3DTileset} for list of valid properties. All options can be used with the exception of url and show which are overridden by values from I3SDataProvider. + */ + /** * An I3SDataProvider is the main public class for I3S support. The url option * should return a scene object. Currently supported I3S versions are 1.6 and * 1.7/1.8 (OGC I3S 1.2). I3SFeature and I3SNode classes implement the * Object Model for I3S entities, with public interfaces. * + *
    + * This object is normally not instantiated directly, use {@link I3SDataProvider.fromUrl}. + *
    + * * @alias I3SDataProvider * @constructor * - * @param {Object} options Object with the following properties: - * @param {Resource|string} options.url The url of the I3S dataset. - * @param {string} [options.name] The name of the I3S dataset. - * @param {boolean} [options.show=true] Determines if the dataset will be shown. - * @param {ArcGISTiledElevationTerrainProvider|Promise} [options.geoidTiledTerrainProvider] Tiled elevation provider describing an Earth Gravitational Model. If defined, geometry will be shifted based on the offsets given by this provider. Required to position I3S data sets with gravity-related height at the correct location. - * @param {boolean} [options.traceFetches=false] Debug option. When true, log a message whenever an I3S tile is fetched. - * @param {Object} [options.cesium3dTilesetOptions] Object containing options to pass to an internally created {@link Cesium3DTileset}. See {@link Cesium3DTileset} for list of valid properties. All options can be used with the exception of url and show which are overridden by values from I3SDataProvider. + * @param {I3SDataProvider.ConstructorOptions} options An object describing initialization options + * + * @see I3SDataProvider.fromUrl + * @see ArcGISTiledElevationTerrainProvider * * @example - * const i3sData = new I3SDataProvider({ - * url: 'https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer/layers/0' - * }); - * viewer.scene.primitives.add(i3sData); + * try { + * const i3sData = await I3SDataProvider.fromUrl( + * "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer/layers/0" + * ); + * viewer.scene.primitives.add(i3sData); + * } catch (error) { + * console.log(`There was an error creating the I3S Data Provider: ${error}`); + * } * * @example - * const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl( - * "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer" - * ); - * let i3sData = new I3SDataProvider({ - * url: 'https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer/layers/0', - * geoidTiledTerrainProvider: geoidService - * }); - * viewer.scene.primitives.add(i3sData); + * try { + * const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl( + * "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer" + * ); + * const i3sData = await I3SDataProvider.fromUrl( + * "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer/layers/0", { + * geoidTiledTerrainProvider: geoidService + * }); + * viewer.scene.primitives.add(i3sData); + * } catch (error) { + * console.log(`There was an error creating the I3S Data Provider: ${error}`); + * } */ function I3SDataProvider(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - //>>includeStart('debug', pragmas.debug); - Check.defined("options.url", options.url); - //>>includeEnd('debug'); - // All public configuration is defined as ES5 properties // These are just the "private" variables and their defaults. - this._resource = Resource.createIfNeeded(options.url); this._name = options.name; this._show = defaultValue(options.show, true); this._geoidTiledTerrainProvider = options.geoidTiledTerrainProvider; @@ -117,13 +135,22 @@ function I3SDataProvider(options) { this._layers = []; this._data = undefined; this._extent = undefined; - this._geoidDataIsReadyPromise = undefined; + this._geoidDataPromise = undefined; this._geoidDataList = undefined; this._decoderTaskProcessor = undefined; - this._readyPromise = undefined; - this._ready = false; + this._taskProcessorReadyPromise = undefined; + + if (defined(options.url)) { + deprecationWarning( + "I3SDataProvider options.url", + "I3SDataProvider constructor parameter options.url was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use I3SDataProvider.fromUrl instead." + ); + this._readyPromise = undefined; + this._ready = false; - this._load(); + this._resource = Resource.createIfNeeded(options.url); + this._load(); + } } Object.defineProperties(I3SDataProvider.prototype, { @@ -200,14 +227,6 @@ Object.defineProperties(I3SDataProvider.prototype, { */ layers: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this.ready) { - throw new DeveloperError( - "The dataset is not loaded. Use I3SDataProvider.readyPromise or wait for I3SDataProvider.ready to be true." - ); - } - //>>includeEnd('debug'); - return this._layers; }, }, @@ -220,14 +239,6 @@ Object.defineProperties(I3SDataProvider.prototype, { */ data: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this.ready) { - throw new DeveloperError( - "The dataset is not loaded. Use I3SDataProvider.readyPromise or wait for I3SDataProvider.ready to be true." - ); - } - //>>includeEnd('debug'); - return this._data; }, }, @@ -240,14 +251,6 @@ Object.defineProperties(I3SDataProvider.prototype, { */ extent: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this.ready) { - throw new DeveloperError( - "The dataset is not loaded. Use I3SDataProvider.readyPromise or wait for I3SDataProvider.ready to be true." - ); - } - //>>includeEnd('debug'); - return this._extent; }, }, @@ -257,9 +260,14 @@ Object.defineProperties(I3SDataProvider.prototype, { * @memberof I3SDataProvider.prototype * @type {Promise} * @readonly + * @deprecated */ readyPromise: { get: function () { + deprecationWarning( + "I3SDataProvider.readyPromise", + "I3SDataProvider.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use I3SDataProvider.fromUrl instead." + ); return this._readyPromise; }, }, @@ -270,9 +278,14 @@ Object.defineProperties(I3SDataProvider.prototype, { * @memberof I3SDataProvider.prototype * @type {boolean} * @readonly + * @deprecated */ ready: { get: function () { + deprecationWarning( + "I3SDataProvider.ready", + "I3SDataProvider.ready was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use I3SDataProvider.fromUrl instead." + ); return this._ready; }, }, @@ -333,9 +346,7 @@ I3SDataProvider.prototype.isDestroyed = function () { */ I3SDataProvider.prototype.update = function (frameState) { for (let i = 0; i < this._layers.length; i++) { - // Reintroducing tileset.ready check to prevent random failures - // when initially loading the tileset - if (defined(this._layers[i]._tileset) && this._layers[i]._tileset.ready) { + if (defined(this._layers[i]._tileset)) { this._layers[i]._tileset.update(frameState); } } @@ -346,7 +357,7 @@ I3SDataProvider.prototype.update = function (frameState) { */ I3SDataProvider.prototype.prePassesUpdate = function (frameState) { for (let i = 0; i < this._layers.length; i++) { - if (defined(this._layers[i]._tileset) && this._layers[i]._tileset.ready) { + if (defined(this._layers[i]._tileset)) { this._layers[i]._tileset.prePassesUpdate(frameState); } } @@ -357,7 +368,7 @@ I3SDataProvider.prototype.prePassesUpdate = function (frameState) { */ I3SDataProvider.prototype.postPassesUpdate = function (frameState) { for (let i = 0; i < this._layers.length; i++) { - if (defined(this._layers[i]._tileset) && this._layers[i]._tileset.ready) { + if (defined(this._layers[i]._tileset)) { this._layers[i]._tileset.postPassesUpdate(frameState); } } @@ -368,40 +379,113 @@ I3SDataProvider.prototype.postPassesUpdate = function (frameState) { */ I3SDataProvider.prototype.updateForPass = function (frameState, passState) { for (let i = 0; i < this._layers.length; i++) { - if (defined(this._layers[i]._tileset) && this._layers[i]._tileset.ready) { + if (defined(this._layers[i]._tileset)) { this._layers[i]._tileset.updateForPass(frameState, passState); } } }; +/** + * Creates an I3SDataProvider. Currently supported I3S versions are 1.6 and + * 1.7/1.8 (OGC I3S 1.2). + * + * @param {string|Resource} url The url of the I3S dataset, which should return an I3S scene object + * @param {I3SDataProvider.ConstructorOptions} options An object describing initialization options + * @returns {Promise} + * + * @example + * try { + * const i3sData = await I3SDataProvider.fromUrl( + * "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer/layers/0" + * ); + * viewer.scene.primitives.add(i3sData); + * } catch (error) { + * console.log(`There was an error creating the I3S Data Provider: ${error}`); + * } + * + * @example + * try { + * const geoidService = await Cesium.ArcGISTiledElevationTerrainProvider.fromUrl( + * "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer" + * ); + * const i3sData = await I3SDataProvider.fromUrl( + * "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer/layers/0", { + * geoidTiledTerrainProvider: geoidService + * }); + * viewer.scene.primitives.add(i3sData); + * } catch (error) { + * console.log(`There was an error creating the I3S Data Provider: ${error}`); + * } + */ +I3SDataProvider.fromUrl = async function (url, options) { + //>>includeStart('debug', pragmas.debug); + Check.defined("url", url); + //>>includeEnd('debug'); + + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + const resource = Resource.createIfNeeded(url); + const data = await I3SDataProvider.loadJson(resource); + + const provider = new I3SDataProvider(options); + provider._resource = resource; + provider._data = data; + + // Success + if (defined(data.layers)) { + for (let layerIndex = 0; layerIndex < data.layers.length; layerIndex++) { + const newLayer = new I3SLayer( + provider, + data.layers[layerIndex], + layerIndex + ); + provider._layers.push(newLayer); + } + } else { + const newLayer = new I3SLayer(provider, data, data.id); + provider._layers.push(newLayer); + } + + provider._computeExtent(); + + // Start loading all of the tiles + const layerPromises = []; + for (let i = 0; i < provider._layers.length; i++) { + layerPromises.push(provider._layers[i].load()); + } + + await Promise.all(layerPromises); + provider._ready = true; + provider._readyPromise = Promise.resolve(provider); + return provider; +}; + /** * @private */ I3SDataProvider.prototype._load = function () { const that = this; - this._readyPromise = this._loadJson(this._resource).then(function (data) { + this._readyPromise = I3SDataProvider.loadJson( + this._resource, + this._traceFetches + ).then(function (data) { // Success that._data = data; - if (defined(that._data.layers)) { - for ( - let layerIndex = 0; - layerIndex < that._data.layers.length; - layerIndex++ - ) { + if (defined(data.layers)) { + for (let layerIndex = 0; layerIndex < data.layers.length; layerIndex++) { const newLayer = new I3SLayer( that, - that._data.layers[layerIndex], + data.layers[layerIndex], layerIndex ); that._layers.push(newLayer); } } else { - const newLayer = new I3SLayer(that, that._data, that._data.id); + const newLayer = new I3SLayer(that, data, data.id); that._layers.push(newLayer); } that._computeExtent(); - that._geoidDataIsReadyPromise = that._loadGeoidData(); // Start loading all of the tiles const layerPromises = []; @@ -427,27 +511,32 @@ I3SDataProvider._fetchJson = function (resource) { /** * @private + * + * @param {Resource} resource The JSON resource to request + * @param {bool=false} trace Log the resource + * @returns {Promise} The fetched data */ -I3SDataProvider.prototype._loadJson = function (resource) { - if (this._traceFetches) { +I3SDataProvider.loadJson = async function (resource, trace) { + if (trace) { console.log("I3S FETCH:", resource.url); } - return I3SDataProvider._fetchJson(resource).then(function (data) { - if (defined(data.error)) { - console.error("Failed to fetch I3S ", resource.url); - if (defined(data.error.message)) { - console.error(data.error.message); - } - if (defined(data.error.details)) { - for (let i = 0; i < data.error.details.length; i++) { - console.log(data.error.details[i]); - } + const data = await I3SDataProvider._fetchJson(resource); + if (defined(data.error)) { + console.error("Failed to fetch I3S ", resource.url); + if (defined(data.error.message)) { + console.error(data.error.message); + } + if (defined(data.error.details)) { + for (let i = 0; i < data.error.details.length; i++) { + console.log(data.error.details[i]); } - return Promise.reject(data.error); } - return data; - }); + + throw new RuntimeError(data.error); + } + + return data; }; /** @@ -496,19 +585,28 @@ I3SDataProvider.prototype._binarizeGltf = function (rawGltf) { /** * @private + * @returns {Promise} */ -I3SDataProvider.prototype._getDecoderTaskProcessor = function () { +I3SDataProvider.prototype.getDecoderTaskProcessor = function () { + if (defined(this._taskProcessorReadyPromise)) { + return this._taskProcessorReadyPromise; + } + if (!defined(this._decoderTaskProcessor)) { const processor = new TaskProcessor("decodeI3S"); - this._taskProcessorReadyPromise = processor.initWebAssemblyModule({ - modulePath: "ThirdParty/Workers/draco_decoder_nodejs.js", - wasmBinaryFile: "ThirdParty/draco_decoder.wasm", - }); + this._taskProcessorReadyPromise = processor + .initWebAssemblyModule({ + modulePath: "ThirdParty/Workers/draco_decoder_nodejs.js", + wasmBinaryFile: "ThirdParty/draco_decoder.wasm", + }) + .then(() => { + return processor; + }); this._decoderTaskProcessor = processor; } - return this._decoderTaskProcessor; + return this._taskProcessorReadyPromise; }; function getCoveredTiles(terrainProvider, extent) { @@ -615,20 +713,34 @@ function getTiles(terrainProvider, extent) { /** * @private */ -I3SDataProvider.prototype._loadGeoidData = async function () { - // Load tiles from arcgis - const that = this; - const geoidTerrainProvider = this._geoidTiledTerrainProvider; - - if (!defined(geoidTerrainProvider)) { - console.log( - "No Geoid Terrain service provided - no geoid conversion will be performed." - ); - return; +I3SDataProvider.prototype.loadGeoidData = async function () { + if (defined(this._geoidDataPromise)) { + return this._geoidDataPromise; } + this._geoidDataPromise = (async () => { + // Load tiles from arcgis + const geoidTerrainProvider = this._geoidTiledTerrainProvider; + + if (!defined(geoidTerrainProvider)) { + console.log( + "No Geoid Terrain service provided - no geoid conversion will be performed." + ); + return; + } - const heightMaps = await getCoveredTiles(geoidTerrainProvider, that._extent); - that._geoidDataList = heightMaps; + try { + const heightMaps = await getCoveredTiles( + geoidTerrainProvider, + this._extent + ); + this._geoidDataList = heightMaps; + } catch (error) { + console.log( + "Error retrieving Geoid Terrain tiles - no geoid conversion will be performed." + ); + } + })(); + return this._geoidDataPromise; }; /** diff --git a/packages/engine/Source/Scene/I3SFeature.js b/packages/engine/Source/Scene/I3SFeature.js index d911a75acaad..e9c2b4926661 100644 --- a/packages/engine/Source/Scene/I3SFeature.js +++ b/packages/engine/Source/Scene/I3SFeature.js @@ -1,4 +1,5 @@ import defined from "../Core/defined.js"; +import I3SDataProvider from "./I3SDataProvider.js"; /** * This class implements an I3S Feature. @@ -52,12 +53,12 @@ Object.defineProperties(I3SFeature.prototype, { * @returns {Promise} A promise that is resolved when the data of the I3S feature is loaded * @private */ -I3SFeature.prototype.load = function () { - const that = this; - return this._dataProvider._loadJson(this._resource).then(function (data) { - that._data = data; - return data; - }); +I3SFeature.prototype.load = async function () { + this._data = await I3SDataProvider.loadJson( + this._resource, + this._dataProvider._traceFetches + ); + return this._data; }; export default I3SFeature; diff --git a/packages/engine/Source/Scene/I3SLayer.js b/packages/engine/Source/Scene/I3SLayer.js index 214e2d3fb903..05828d8617a0 100644 --- a/packages/engine/Source/Scene/I3SLayer.js +++ b/packages/engine/Source/Scene/I3SLayer.js @@ -175,7 +175,7 @@ I3SLayer.prototype.load = async function () { ); } - await this._dataProvider._geoidDataIsReadyPromise; + await this._dataProvider.loadGeoidData(); await this._loadRootNode(); await this._create3DTileset(); diff --git a/packages/engine/Source/Scene/I3SNode.js b/packages/engine/Source/Scene/I3SNode.js index 839b0517a68c..222f79ce7e59 100644 --- a/packages/engine/Source/Scene/I3SNode.js +++ b/packages/engine/Source/Scene/I3SNode.js @@ -1,6 +1,5 @@ import Cartographic from "../Core/Cartographic.js"; import defaultValue from "../Core/defaultValue.js"; -import defer from "../Core/defer.js"; import defined from "../Core/defined.js"; import Ellipsoid from "../Core/Ellipsoid.js"; import HeadingPitchRoll from "../Core/HeadingPitchRoll.js"; @@ -11,6 +10,7 @@ import Resource from "../Core/Resource.js"; import Quaternion from "../Core/Quaternion.js"; import Transforms from "../Core/Transforms.js"; import Cesium3DTile from "./Cesium3DTile.js"; +import I3SDataProvider from "./I3SDataProvider.js"; import I3SFeature from "./I3SFeature.js"; import I3SField from "./I3SField.js"; import I3SGeometry from "./I3SGeometry.js"; @@ -52,6 +52,7 @@ function I3SNode(parent, ref, isRoot) { this._layer = layer; this._nodeIndex = nodeIndex; this._resource = resource; + this._isLoading = false; this._tile = undefined; this._data = undefined; @@ -170,7 +171,7 @@ Object.defineProperties(I3SNode.prototype, { /** * @private */ -I3SNode.prototype.load = function () { +I3SNode.prototype.load = async function () { const that = this; function processData() { @@ -191,28 +192,29 @@ I3SNode.prototype.load = function () { // If we don't have a nodepage index load from json if (!defined(this._nodeIndex)) { - return this._dataProvider._loadJson(this._resource).then(function (data) { - // Success - that._data = data; - processData(); - }); + const data = await I3SDataProvider.loadJson( + this._resource, + this._dataProvider._traceFetches + ); + that._data = data; + processData(); + return; } - return this._layer._getNodeInNodePages(this._nodeIndex).then(function (node) { - that._data = node; - let uri; - if (that._isRoot) { - uri = "nodes/root/"; - } else if (defined(node.mesh)) { - const uriIndex = node.mesh.geometry.resource; - uri = `../${uriIndex}/`; - } - if (defined(uri)) { - that._resource = that._parent.resource.getDerivedResource({ url: uri }); - } + const node = await this._layer._getNodeInNodePages(this._nodeIndex); + that._data = node; + let uri; + if (that._isRoot) { + uri = "nodes/root/"; + } else if (defined(node.mesh)) { + const uriIndex = node.mesh.geometry.resource; + uri = `../${uriIndex}/`; + } + if (defined(uri)) { + that._resource = that._parent.resource.getDerivedResource({ url: uri }); + } - processData(); - }); + processData(); }; /** @@ -609,7 +611,10 @@ I3SNode.prototype._create3DTileDefinition = function () { /** * @private */ -I3SNode.prototype._createI3SDecoderTask = function (dataProvider, data) { +I3SNode.prototype._createI3SDecoderTask = async function ( + decodeI3STaskProcessor, + data +) { // Prepare the data to send to the worker const parentData = data.geometryData._parent._data; const parentRotationInverseMatrix = @@ -664,18 +669,14 @@ I3SNode.prototype._createI3SDecoderTask = function (dataProvider, data) { parentRotation: parentRotation, }; - const decodeI3STaskProcessor = dataProvider._getDecoderTaskProcessor(); - const transferrableObjects = []; - return dataProvider._taskProcessorReadyPromise.then(function () { - return decodeI3STaskProcessor.scheduleTask(payload, transferrableObjects); - }); + return decodeI3STaskProcessor.scheduleTask(payload, transferrableObjects); }; /** * @private */ -I3SNode.prototype._createContentURL = function () { +I3SNode.prototype._createContentURL = async function () { let rawGltf = { scene: 0, scenes: [ @@ -701,6 +702,8 @@ I3SNode.prototype._createContentURL = function () { }, }; + const decodeI3STaskProcessor = await this._dataProvider.getDecoderTaskProcessor(); + // Load the geometry data const dataPromises = [this._loadGeometryData()]; if (this._dataProvider.legacyVersion16) { @@ -720,13 +723,16 @@ I3SNode.prototype._createContentURL = function () { tile: that._tile, }; - const task = that._createI3SDecoderTask(that._dataProvider, parameters); - if (!defined(task)) { + const promise = that._createI3SDecoderTask( + decodeI3STaskProcessor, + parameters + ); + if (!defined(promise)) { // Postponed return; } - generateGltfPromise = task.then(function (result) { + generateGltfPromise = promise.then(function (result) { rawGltf = parameters.geometryData._generateGltf( result.meshData.nodesInScene, result.meshData.nodes, @@ -746,7 +752,7 @@ I3SNode.prototype._createContentURL = function () { const glbDataBlob = new Blob([binaryGltfData], { type: "application/binary", }); - that._glbURL = URL.createObjectURL(glbDataBlob); + return URL.createObjectURL(glbDataBlob); }); }); }; @@ -757,50 +763,37 @@ Cesium3DTile.prototype._hookedRequestContent = Cesium3DTile.prototype.requestContent; /** + * Requests the tile's content. + *

    + * The request may not be made if the Cesium Request Scheduler can't prioritize it. + *

    + * + * @return {Promise|undefined} A promise that resolves when the request completes, or undefined if there is no request needed, or the request cannot be scheduled. * @private */ -Cesium3DTile.prototype._resolveHookedObject = function () { - const that = this; - // Keep a handle on the early promises - // Call the real requestContent function - this._hookedRequestContent(); - - // Fulfill the promises - this._contentReadyToProcessPromise.then(function () { - that._contentReadyToProcessDefer.resolve(); - }); - - this._contentReadyPromise.then(function (content) { - that._isLoading = false; - that._contentReadyDefer.resolve(content); - }); -}; - Cesium3DTile.prototype.requestContent = function () { - const that = this; if (!this.tileset._isI3STileSet) { return this._hookedRequestContent(); } if (!this._isLoading) { this._isLoading = true; - - // Create early promises that will be fulfilled later - this._contentReadyToProcessDefer = defer(); - this._contentReadyDefer = defer(); - this._contentReadyToProcessPromise = this._contentReadyToProcessDefer.promise; - this._contentReadyPromise = this._contentReadyDefer.promise; - - this._i3sNode._createContentURL().then(function () { - that._contentResource = new Resource({ url: that._i3sNode._glbURL }); - that._resolveHookedObject(); - }); - - // Returns the number of requests - return 0; + return this._i3sNode + ._createContentURL() + .then((url) => { + if (!defined(url)) { + this._isLoading = false; + return; + } + + this._contentResource = new Resource({ url: url }); + return this._hookedRequestContent(); + }) + .then((content) => { + this._isLoading = false; + return content; + }); } - - return 1; }; function bilinearInterpolate(tx, ty, h00, h10, h01, h11) { diff --git a/packages/engine/Source/Scene/PointCloud.js b/packages/engine/Source/Scene/PointCloud.js index a5e94aad2fbc..c2a0d363cd3b 100644 --- a/packages/engine/Source/Scene/PointCloud.js +++ b/packages/engine/Source/Scene/PointCloud.js @@ -156,9 +156,8 @@ function PointCloud(options) { ); this._splittingEnabled = false; - this._resolveReadyPromise = undefined; - this._rejectReadyPromise = undefined; - this._readyPromise = initialize(this, options); + this._error = undefined; + initialize(this, options); } Object.defineProperties(PointCloud.prototype, { @@ -180,12 +179,6 @@ Object.defineProperties(PointCloud.prototype, { }, }, - readyPromise: { - get: function () { - return this._readyPromise; - }, - }, - color: { get: function () { return Color.clone(this._highlightColor); @@ -283,14 +276,6 @@ function initialize(pointCloud, options) { } pointCloud._pointsLength = parsedContent.pointsLength; - - return new Promise(function (resolve, reject) { - pointCloud._resolveReadyPromise = function () { - pointCloud._ready = true; - resolve(pointCloud); - }; - pointCloud._rejectReadyPromise = reject; - }); } const scratchMin = new Cartesian3(); @@ -1280,7 +1265,7 @@ function decodeDraco(pointCloud, context) { }) .catch(function (error) { pointCloud._decodingState = DecodingState.FAILED; - pointCloud._rejectReadyPromise(error); + pointCloud._error = error; }); } } @@ -1292,6 +1277,13 @@ const scratchScale = new Cartesian3(); PointCloud.prototype.update = function (frameState) { const context = frameState.context; + + if (defined(this._error)) { + const error = this._error; + this._error = undefined; + throw error; + } + const decoding = decodeDraco(this, context); if (decoding) { return; @@ -1309,7 +1301,7 @@ PointCloud.prototype.update = function (frameState) { createResources(this, frameState); modelMatrixDirty = true; shadersDirty = true; - this._resolveReadyPromise(); + this._ready = true; this._parsedContent = undefined; // Unload } diff --git a/packages/engine/Source/Scene/Primitive.js b/packages/engine/Source/Scene/Primitive.js index a431decf52b1..3dabc034a1ca 100644 --- a/packages/engine/Source/Scene/Primitive.js +++ b/packages/engine/Source/Scene/Primitive.js @@ -9,6 +9,7 @@ import combine from "../Core/combine.js"; import ComponentDatatype from "../Core/ComponentDatatype.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import EncodedCartesian3 from "../Core/EncodedCartesian3.js"; @@ -350,6 +351,7 @@ function Primitive(options) { this._ready = false; const primitive = this; + // This is here for backwards compatibility. This promise wrapper can be removed once readyPromise is removed. this._readyPromise = new Promise((resolve, reject) => { primitive._completeLoad = (frameState, state, error) => { this._error = error; @@ -486,6 +488,19 @@ Object.defineProperties(Primitive.prototype, { * * @type {boolean} * @readonly + * + * @example + * // Wait for a primitive to become ready before accessing attributes + * const removeListener = scene.postRender.addEventListener(() => { + * if (!frustumPrimitive.ready) { + * return; + * } + * + * const attributes = primitive.getGeometryInstanceAttributes('an id'); + * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA); + * + * removeListener(); + * }); */ ready: { get: function () { @@ -498,9 +513,14 @@ Object.defineProperties(Primitive.prototype, { * @memberof Primitive.prototype * @type {Promise} * @readonly + * @deprecated */ readyPromise: { get: function () { + deprecationWarning( + "Primitive.readyPromise", + "Primitive.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Primitive.ready to return true instead." + ); return this._readyPromise; }, }, diff --git a/packages/engine/Source/Scene/TimeDynamicPointCloud.js b/packages/engine/Source/Scene/TimeDynamicPointCloud.js index 04b3f77cab57..6d6d1b87acfb 100644 --- a/packages/engine/Source/Scene/TimeDynamicPointCloud.js +++ b/packages/engine/Source/Scene/TimeDynamicPointCloud.js @@ -3,6 +3,7 @@ import combine from "../Core/combine.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import Event from "../Core/Event.js"; import getTimestamp from "../Core/getTimestamp.js"; import JulianDate from "../Core/JulianDate.js"; @@ -183,6 +184,7 @@ function TimeDynamicPointCloud(options) { this._clockMultiplier = 0.0; this._resolveReadyPromise = undefined; const that = this; + // This is here for backwards compatibility and can be removed when readyPromise is removed. this._readyPromise = new Promise(function (resolve) { that._resolveReadyPromise = resolve; }); @@ -252,9 +254,14 @@ Object.defineProperties(TimeDynamicPointCloud.prototype, { * * @type {Promise} * @readonly + * @deprecated */ readyPromise: { get: function () { + deprecationWarning( + "TimeDynamicPointCloud.readyPromise", + "TimeDynamicPointCloud.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use TimeDynamicPointCloud.frameFailed instead." + ); return this._readyPromise; }, }, @@ -395,6 +402,7 @@ function requestFrame(that, interval, frameState) { sequential: true, ready: false, touchedFrameNumber: frameState.frameNumber, + uri: uri, }; frames[index] = frame; Resource.fetchArrayBuffer({ @@ -410,7 +418,6 @@ function requestFrame(that, interval, frameState) { uniformMapLoaded: getUniformMapLoaded(that), pickIdLoaded: getPickIdLoaded, }); - return frame.pointCloud.readyPromise; }) .catch(handleFrameFailure(that, uri)); } @@ -507,7 +514,12 @@ function renderFrame(that, frame, updateState, frameState) { pointCloud.geometricErrorScale = shading.geometricErrorScale; pointCloud.maximumAttenuation = getMaximumAttenuation(that); - pointCloud.update(frameState); + try { + pointCloud.update(frameState); + } catch (error) { + handleFrameFailure(that, frame.uri)(error); + } + frame.touchedFrameNumber = frameState.frameNumber; } @@ -738,6 +750,7 @@ TimeDynamicPointCloud.prototype.update = function (frameState) { const that = this; if (defined(frame) && !defined(this._lastRenderedFrame)) { frameState.afterRender.push(function () { + // This is here for backwards compatibility and can be removed when readyPromise is removed. that._resolveReadyPromise(that); return true; }); diff --git a/packages/engine/Source/Scene/VoxelContent.js b/packages/engine/Source/Scene/VoxelContent.js index eead3e8f72d3..4d566c89d0d0 100644 --- a/packages/engine/Source/Scene/VoxelContent.js +++ b/packages/engine/Source/Scene/VoxelContent.js @@ -11,58 +11,20 @@ import MetadataTable from "./MetadataTable.js"; * @constructor * * @param {Resource} resource The resource for this voxel content. This is used for fetching external buffers as needed. - * @param {object} [json] Voxel JSON contents. Mutually exclusive with binary. - * @param {Uint8Array} [binary] Voxel binary contents. Mutually exclusive with json. - * @param {MetadataSchema} metadataSchema The metadata schema used by property tables in the voxel content - * - * @exception {DeveloperError} One of json and binary must be defined. * * @private * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy. */ -function VoxelContent(resource, json, binary, metadataSchema) { +function VoxelContent(resource) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("resource", resource); - if (defined(json) === defined(binary)) { - throw new DeveloperError("One of json and binary must be defined."); - } //>>includeEnd('debug'); this._resource = resource; this._metadataTable = undefined; - - let chunks; - if (defined(json)) { - chunks = { - json: json, - binary: undefined, - }; - } else { - chunks = parseVoxelChunks(binary); - } - - this._readyPromise = initialize( - this, - chunks.json, - chunks.binary, - metadataSchema - ); } Object.defineProperties(VoxelContent.prototype, { - /** - * A promise that resolves once all buffers are loaded. - * - * @type {Promise} - * @readonly - * @private - */ - readyPromise: { - get: function () { - return this._readyPromise; - }, - }, - /** * The {@link MetadataTable} storing voxel property values. * @@ -77,38 +39,74 @@ Object.defineProperties(VoxelContent.prototype, { }, }); -function initialize(content, json, binary, metadataSchema) { - return requestBuffers(content, json, binary).then(function (buffersU8) { - const bufferViewsU8 = {}; - const bufferViewsLength = json.bufferViews.length; - for (let i = 0; i < bufferViewsLength; ++i) { - const bufferViewJson = json.bufferViews[i]; - const start = bufferViewJson.byteOffset; - const end = start + bufferViewJson.byteLength; - const buffer = buffersU8[bufferViewJson.buffer]; - const bufferView = buffer.subarray(start, end); - bufferViewsU8[i] = bufferView; - } +/** + * Creates an object representing voxel content for a {@link Cesium3DTilesVoxelProvider}. + * + * @param {Resource} resource The resource for this voxel content. This is used for fetching external buffers as needed. + * @param {object} [json] Voxel JSON contents. Mutually exclusive with binary. + * @param {Uint8Array} [binary] Voxel binary contents. Mutually exclusive with json. + * @param {MetadataSchema} metadataSchema The metadata schema used by property tables in the voxel content + * @returns {Promise} + * + * @exception {DeveloperError} One of json and binary must be defined. + */ +VoxelContent.fromJson = async function ( + resource, + json, + binary, + metadataSchema +) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("resource", resource); + if (defined(json) === defined(binary)) { + throw new DeveloperError("One of json and binary must be defined."); + } + //>>includeEnd('debug'); + + let chunks; + if (defined(json)) { + chunks = { + json: json, + binary: undefined, + }; + } else { + chunks = parseVoxelChunks(binary); + } + + const buffersU8 = await requestBuffers(resource, chunks.json, chunks.binary); + const bufferViewsU8 = {}; + const bufferViewsLength = chunks.json.bufferViews.length; + for (let i = 0; i < bufferViewsLength; ++i) { + const bufferViewJson = chunks.json.bufferViews[i]; + const start = bufferViewJson.byteOffset; + const end = start + bufferViewJson.byteLength; + const buffer = buffersU8[bufferViewJson.buffer]; + const bufferView = buffer.subarray(start, end); + bufferViewsU8[i] = bufferView; + } + + const propertyTableIndex = chunks.json.voxelTable; + const propertyTableJson = chunks.json.propertyTables[propertyTableIndex]; - const propertyTableIndex = json.voxelTable; - const propertyTableJson = json.propertyTables[propertyTableIndex]; - content._metadataTable = new MetadataTable({ - count: propertyTableJson.count, - properties: propertyTableJson.properties, - class: metadataSchema.classes[propertyTableJson.class], - bufferViews: bufferViewsU8, - }); - return content; + const content = new VoxelContent(resource); + + content._metadataTable = new MetadataTable({ + count: propertyTableJson.count, + properties: propertyTableJson.properties, + class: metadataSchema.classes[propertyTableJson.class], + bufferViews: bufferViewsU8, }); -} -function requestBuffers(content, json, binary) { + return content; +}; + +function requestBuffers(resource, json, binary) { const buffersLength = json.buffers.length; const bufferPromises = new Array(buffersLength); for (let i = 0; i < buffersLength; i++) { const buffer = json.buffers[i]; if (defined(buffer.uri)) { - const baseResource = content._resource; + const baseResource = resource; const bufferResource = baseResource.getDerivedResource({ url: buffer.uri, }); diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js index cd862874da0d..833d764142cf 100644 --- a/packages/engine/Source/Scene/VoxelPrimitive.js +++ b/packages/engine/Source/Scene/VoxelPrimitive.js @@ -8,8 +8,8 @@ import clone from "../Core/clone.js"; import Color from "../Core/Color.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; -import DeveloperError from "../Core/DeveloperError.js"; import Event from "../Core/Event.js"; import JulianDate from "../Core/JulianDate.js"; import Matrix3 from "../Core/Matrix3.js"; @@ -426,7 +426,7 @@ function VoxelPrimitive(options) { this._readyPromise = initialize(this, provider); } -function initialize(primitive, provider) { +async function initialize(primitive, provider) { const promise = new Promise(function (resolve) { primitive._completeLoad = function (primitive, frameState) { // Set the primitive as ready after the first frame render since the user might set up events subscribed to @@ -439,9 +439,35 @@ function initialize(primitive, provider) { }; }); - return provider.readyPromise.then(function () { - return promise; - }); + // This is here for backwards compatibility. It can be removed when readyPromise is removed. + if (defined(provider._readyPromise) && !provider._ready) { + await provider._readyPromise; + } + + // Set the bounds + const { + shape: shapeType, + minBounds = VoxelShapeType.getMinBounds(shapeType), + maxBounds = VoxelShapeType.getMaxBounds(shapeType), + } = provider; + + primitive.minBounds = minBounds; + primitive.maxBounds = maxBounds; + primitive.minClippingBounds = VoxelShapeType.getMinBounds(shapeType); + primitive.maxClippingBounds = VoxelShapeType.getMaxBounds(shapeType); + + checkTransformAndBounds(primitive, provider); + + // Create the shape object, and update it so it is valid for VoxelTraversal + const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType); + primitive._shape = new ShapeConstructor(); + primitive._shapeVisible = updateShapeAndTransforms( + primitive, + primitive._shape, + provider + ); + + return promise; } Object.defineProperties(VoxelPrimitive.prototype, { @@ -464,9 +490,14 @@ Object.defineProperties(VoxelPrimitive.prototype, { * @memberof VoxelPrimitive.prototype * @type {Promise} * @readonly + * @deprecated */ readyPromise: { get: function () { + deprecationWarning( + "VoxelPrimitive.readyPromise", + "VoxelPrimitive.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for VoxelPrimitive.ready to return true instead." + ); return this._readyPromise; }, }, @@ -490,19 +521,9 @@ Object.defineProperties(VoxelPrimitive.prototype, { * @memberof VoxelPrimitive.prototype * @type {BoundingSphere} * @readonly - * - * @exception {DeveloperError} If the primitive is not ready. */ boundingSphere: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError( - "boundingSphere must not be called before the primitive is ready." - ); - } - //>>includeEnd('debug'); - return this._shape.boundingSphere; }, }, @@ -513,20 +534,10 @@ Object.defineProperties(VoxelPrimitive.prototype, { * @memberof VoxelPrimitive.prototype * @type {OrientedBoundingBox} * @readonly - * - * @exception {DeveloperError} If the primitive is not ready. */ orientedBoundingBox: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError( - "orientedBoundingBox must not be called before the primitive is ready." - ); - } - //>>includeEnd('debug'); - - return this._shape.orientedBoundingBox; + return this.shape.orientedBoundingBox; }, }, @@ -556,19 +567,9 @@ Object.defineProperties(VoxelPrimitive.prototype, { * @memberof VoxelPrimitive.prototype * @type {VoxelShapeType} * @readonly - * - * @exception {DeveloperError} If the primitive is not ready. */ shape: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError( - "shape must not be called before the primitive is ready." - ); - } - //>>includeEnd('debug'); - return this._provider.shape; }, }, @@ -579,19 +580,9 @@ Object.defineProperties(VoxelPrimitive.prototype, { * @memberof VoxelPrimitive.prototype * @type {Cartesian3} * @readonly - * - * @exception {DeveloperError} If the primitive is not ready. */ dimensions: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError( - "dimensions must not be called before the primitive is ready." - ); - } - //>>includeEnd('debug'); - return this._provider.dimensions; }, }, @@ -602,19 +593,9 @@ Object.defineProperties(VoxelPrimitive.prototype, { * @memberof VoxelPrimitive.prototype * @type {number[][]} * @readonly - * - * @exception {DeveloperError} If the primitive is not ready. */ minimumValues: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError( - "minimumValues must not be called before the primitive is ready." - ); - } - //>>includeEnd('debug'); - return this._provider.minimumValues; }, }, @@ -625,19 +606,9 @@ Object.defineProperties(VoxelPrimitive.prototype, { * @memberof VoxelPrimitive.prototype * @type {number[][]} * @readonly - * - * @exception {DeveloperError} If the primitive is not ready. */ maximumValues: { get: function () { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError( - "maximumValues must not be called before the primitive is ready." - ); - } - //>>includeEnd('debug'); - return this._provider.maximumValues; }, }, @@ -1018,7 +989,8 @@ VoxelPrimitive.prototype.update = function (frameState) { this._customShader.update(frameState); // Exit early if it's not ready yet. - if (!this._ready && !provider.ready) { + // This is here for backward compatibility. It can be removed when readyPromise is removed. + if ((defined(provider._ready) && !provider._ready) || !defined(this._shape)) { return; } @@ -1188,29 +1160,6 @@ function initFromProvider(primitive, provider, context) { primitive._pickId = context.createPickId({ primitive }); uniforms.pickColor = Color.clone(primitive._pickId.color, uniforms.pickColor); - // Set the bounds - const { - shape: shapeType, - minBounds = VoxelShapeType.getMinBounds(shapeType), - maxBounds = VoxelShapeType.getMaxBounds(shapeType), - } = provider; - - primitive.minBounds = minBounds; - primitive.maxBounds = maxBounds; - primitive.minClippingBounds = VoxelShapeType.getMinBounds(shapeType); - primitive.maxClippingBounds = VoxelShapeType.getMaxBounds(shapeType); - - checkTransformAndBounds(primitive, provider); - - // Create the shape object, and update it so it is valid for VoxelTraversal - const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType); - primitive._shape = new ShapeConstructor(); - primitive._shapeVisible = updateShapeAndTransforms( - primitive, - primitive._shape, - provider - ); - const { shaderDefines, shaderUniforms: shapeUniforms } = primitive._shape; primitive._shapeDefinesOld = clone(shaderDefines, true); @@ -1896,7 +1845,6 @@ VoxelPrimitive.DefaultCustomShader = new CustomShader({ function DefaultVoxelProvider() { this.ready = true; - this.readyPromise = Promise.resolve(this); this.shape = VoxelShapeType.BOX; this.dimensions = new Cartesian3(1, 1, 1); this.names = ["data"]; diff --git a/packages/engine/Source/Scene/VoxelProvider.js b/packages/engine/Source/Scene/VoxelProvider.js index 48a20b1e9d29..0f48173c9248 100644 --- a/packages/engine/Source/Scene/VoxelProvider.js +++ b/packages/engine/Source/Scene/VoxelProvider.js @@ -24,6 +24,7 @@ Object.defineProperties(VoxelProvider.prototype, { * @memberof VoxelProvider.prototype * @type {boolean} * @readonly + * @deprecated */ ready: { get: DeveloperError.throwInstantiationError, @@ -35,6 +36,7 @@ Object.defineProperties(VoxelProvider.prototype, { * @memberof VoxelProvider.prototype * @type {Promise} * @readonly + * @deprecated */ readyPromise: { get: DeveloperError.throwInstantiationError, @@ -247,8 +249,6 @@ Object.defineProperties(VoxelProvider.prototype, { * @param {number} [options.tileZ=0] The tile's Z coordinate. * @privateparam {number} [options.keyframe=0] The requested keyframe. * @returns {Promise|undefined} A promise to an array of typed arrays containing the requested voxel data or undefined if there was a problem loading the data. - * - * @exception {DeveloperError} The provider must be ready. */ VoxelProvider.prototype.requestData = DeveloperError.throwInstantiationError; diff --git a/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js index bea961f4e89f..ccb33d20733e 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js @@ -26,9 +26,10 @@ describe("Scene/Cesium3DTilesVoxelProvider", function () { url: url, }); + expect(provider).toBeDefined(); + return provider.readyPromise.then(function () { expect(provider).toBeDefined(); - expect(provider.ready).toBeTrue(); expect(provider.globalTransform).toEqual(Matrix4.IDENTITY); expect(provider.shapeTransform).toEqualEpsilon( Matrix4.fromScale(Ellipsoid.WGS84.radii), @@ -48,61 +49,52 @@ describe("Scene/Cesium3DTilesVoxelProvider", function () { }); }); - it("constructor throws when url option is missing", function () { - expect(function () { - return new Cesium3DTilesVoxelProvider({ - url: undefined, - }); - }).toThrowDeveloperError(); - }); - - it("requestData works for root tile", function () { + it("fromUrl creates a voxel provider", async function () { const url = "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json"; - const provider = new Cesium3DTilesVoxelProvider({ - url: url, - }); + const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); - return provider.readyPromise - .then(function () { - return provider.requestData(); - }) - .then(function (data) { - expect(data.length).toEqual(1); - - const dimensions = provider.dimensions; - const voxelCount = dimensions.x * dimensions.y * dimensions.z; - const componentCount = MetadataType.getComponentCount( - provider.types[0] - ); - const expectedLength = voxelCount * componentCount; - expect(data[0].length).toEqual(expectedLength); - }); + expect(provider).toBeInstanceOf(Cesium3DTilesVoxelProvider); + expect(provider.globalTransform).toEqual(Matrix4.IDENTITY); + expect(provider.shapeTransform).toEqualEpsilon( + Matrix4.fromScale(Ellipsoid.WGS84.radii), + CesiumMath.EPSILON10 + ); + expect(provider.shape).toEqual(VoxelShapeType.ELLIPSOID); + expect(provider.minBounds).toEqual(new Cartesian3(0.0, 0.0, -1.0)); + expect(provider.maxBounds).toEqual(new Cartesian3(1.0, 1.0, 0.0)); + expect(provider.dimensions).toEqual(new Cartesian3(2, 2, 2)); + expect(provider.paddingBefore).toBeUndefined(); + expect(provider.paddingAfter).toBeUndefined(); + expect(provider.names).toEqual(["a"]); + expect(provider.types).toEqual([MetadataType.SCALAR]); + expect(provider.componentTypes).toEqual([MetadataComponentType.FLOAT32]); + expect(provider.minimumValues).toEqual([[0]]); + expect(provider.maximumValues).toEqual([[1]]); }); - it("requestData throws if the provider is not ready", function () { + it("requestData works for root tile", async function () { const url = "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json"; - const provider = new Cesium3DTilesVoxelProvider({ - url: url, - }); - expect(function () { - return provider.requestData(); - }).toThrowDeveloperError(); + const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); + + const data = await provider.requestData(); + expect(data.length).toEqual(1); + + const dimensions = provider.dimensions; + const voxelCount = dimensions.x * dimensions.y * dimensions.z; + const componentCount = MetadataType.getComponentCount(provider.types[0]); + const expectedLength = voxelCount * componentCount; + expect(data[0].length).toEqual(expectedLength); }); - it("requestData loads multiple attributes correctly", function () { + it("requestData loads multiple attributes correctly", async function () { const url = "./Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tileset.json"; - const provider = new Cesium3DTilesVoxelProvider({ url }); + const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); - return provider.readyPromise - .then(function () { - return provider.requestData(); - }) - .then(function (data) { - expect(data.length).toBe(3); - expect(data[0][0]).toBe(0.0); - expect(data[1][0]).toBe(0.5); - expect(data[2][0]).toBe(1.0); - }); + const data = await provider.requestData(); + expect(data.length).toBe(3); + expect(data[0][0]).toBe(0.0); + expect(data[1][0]).toBe(0.5); + expect(data[2][0]).toBe(1.0); }); }); diff --git a/packages/engine/Specs/Scene/I3SDataProviderSpec.js b/packages/engine/Specs/Scene/I3SDataProviderSpec.js index 50a7999be80a..780ff4b98343 100644 --- a/packages/engine/Specs/Scene/I3SDataProviderSpec.js +++ b/packages/engine/Specs/Scene/I3SDataProviderSpec.js @@ -1,9 +1,11 @@ import { + Cesium3DTileset, GeographicTilingScheme, I3SDataProvider, Math as CesiumMath, Rectangle, Resource, + RuntimeError, } from "../../index.js"; describe("Scene/I3SDataProvider", function () { @@ -182,8 +184,9 @@ describe("Scene/I3SDataProvider", function () { const frameState = {}; testProvider.update(frameState); - // Function should not be called for tilesets that are not yet ready - expect(testProvider._layers[0]._tileset.update).not.toHaveBeenCalled(); + expect(testProvider._layers[0]._tileset.update).toHaveBeenCalledWith( + frameState + ); expect(testProvider._layers[1]._tileset.update).toHaveBeenCalledWith( frameState ); @@ -203,10 +206,9 @@ describe("Scene/I3SDataProvider", function () { const frameState = {}; testProvider.prePassesUpdate(frameState); - // Function should not be called for tilesets that are not yet ready expect( testProvider._layers[0]._tileset.prePassesUpdate - ).not.toHaveBeenCalled(); + ).toHaveBeenCalledWith(frameState); expect( testProvider._layers[1]._tileset.prePassesUpdate ).toHaveBeenCalledWith(frameState); @@ -226,10 +228,9 @@ describe("Scene/I3SDataProvider", function () { const frameState = {}; testProvider.postPassesUpdate(frameState); - // Function should not be called for tilesets that are not yet ready expect( testProvider._layers[0]._tileset.postPassesUpdate - ).not.toHaveBeenCalled(); + ).toHaveBeenCalledWith(frameState); expect( testProvider._layers[1]._tileset.postPassesUpdate ).toHaveBeenCalledWith(frameState); @@ -250,10 +251,10 @@ describe("Scene/I3SDataProvider", function () { const passState = { test: "test" }; testProvider.updateForPass(frameState, passState); - // Function should not be called for tilesets that are not yet ready - expect( - testProvider._layers[0]._tileset.updateForPass - ).not.toHaveBeenCalled(); + expect(testProvider._layers[0]._tileset.updateForPass).toHaveBeenCalledWith( + frameState, + passState + ); expect(testProvider._layers[1]._tileset.updateForPass).toHaveBeenCalledWith( frameState, passState @@ -342,12 +343,9 @@ describe("Scene/I3SDataProvider", function () { Promise.resolve(mockBinaryResponse) ); - spyOn(console, "log"); - const resource = Resource.createIfNeeded("mockBinaryUri"); return testProvider._loadBinary(resource).then(function (result) { expect(Resource.prototype.fetchArrayBuffer).toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("I3S FETCH:", resource.url); expect(result).toBe(mockBinaryResponse); }); }); @@ -370,69 +368,38 @@ describe("Scene/I3SDataProvider", function () { }); }); - it("loads json", function () { - const mockJsonResponse = { test: 1 }; - - spyOn(I3SDataProvider.prototype, "_load"); - const testProvider = new I3SDataProvider({ - url: "mockProviderUrl", - name: "testProvider", - }); + it("fromUrl throws without url ", async function () { + await expectAsync( + I3SDataProvider.fromUrl() + ).toBeRejectedWithDeveloperError(); + }); + it("loads json", async function () { spyOn(Resource.prototype, "fetchJson").and.returnValue( - Promise.resolve(mockJsonResponse) + Promise.resolve(mockProviderData) ); - - const resource = Resource.createIfNeeded("mockJsonUri"); - return testProvider._loadJson(resource).then(function (result) { - expect(Resource.prototype.fetchJson).toHaveBeenCalled(); - expect(result).toBe(mockJsonResponse); + spyOn(Cesium3DTileset, "fromUrl").and.callFake(async () => { + const tileset = new Cesium3DTileset(); + tileset._root = {}; // Mock the root tile so that i3s property can be appended + return tileset; }); - }); - it("loads json with traceFetches enabled", function () { - const mockJsonResponse = { test: 1 }; - - spyOn(I3SDataProvider.prototype, "_load"); - const testProvider = new I3SDataProvider({ - url: "mockProviderUrl", + const testProvider = await I3SDataProvider.fromUrl("mockProviderUrl", { name: "testProvider", - traceFetches: true, }); - spyOn(Resource.prototype, "fetchJson").and.returnValue( - Promise.resolve(mockJsonResponse) - ); - - spyOn(console, "log"); - - const resource = Resource.createIfNeeded("mockJsonUri"); - return testProvider._loadJson(resource).then(function (result) { - expect(Resource.prototype.fetchJson).toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("I3S FETCH:", resource.url); - expect(result).toBe(mockJsonResponse); - }); + expect(Resource.prototype.fetchJson).toHaveBeenCalled(); + expect(testProvider).toBeInstanceOf(I3SDataProvider); + expect(testProvider.data).toEqual(mockProviderData); + expect(Cesium3DTileset.fromUrl).toHaveBeenCalled(); }); - it("loadJson rejects invalid uri", function () { - spyOn(I3SDataProvider.prototype, "_load"); - const testProvider = new I3SDataProvider({ - url: "mockProviderUrl", - name: "testProvider", - }); - + it("loadJson rejects invalid uri", async function () { const resource = Resource.createIfNeeded("mockJsonUri"); - return testProvider - ._loadJson(resource) - .then(function () { - fail("Promise should not be resolved for invalid uri"); - }) - .catch(function (error) { - expect(error.statusCode).toEqual(404); - }); + await expectAsync(I3SDataProvider.loadJson(resource)).toBeRejected(); }); - it("loadJson rejects error response", function () { + it("loadJson rejects error response", async function () { const mockErrorResponse = { error: { code: 498, @@ -444,25 +411,15 @@ describe("Scene/I3SDataProvider", function () { }, }; - spyOn(I3SDataProvider.prototype, "_load"); - const testProvider = new I3SDataProvider({ - url: "mockProviderUrl", - name: "testProvider", - }); - spyOn(Resource.prototype, "fetchJson").and.returnValue( Promise.resolve(mockErrorResponse) ); const resource = Resource.createIfNeeded("mockJsonUri"); - return testProvider - ._loadJson(resource) - .then(function () { - fail("Promise should not be resolved for error response"); - }) - .catch(function (error) { - expect(error).toBe(mockErrorResponse.error); - }); + await expectAsync(I3SDataProvider.loadJson(resource)).toBeRejectedWithError( + RuntimeError, + mockErrorResponse.error + ); }); it("loads geoid data", function () { @@ -475,7 +432,7 @@ describe("Scene/I3SDataProvider", function () { testProvider._extent = Rectangle.fromDegrees(-1, 0, 1, 2); - return testProvider._loadGeoidData().then(function () { + return testProvider.loadGeoidData().then(function () { expect(testProvider._geoidDataList.length).toEqual(2); expect(testProvider._geoidDataList[0].height).toEqual(2); expect(testProvider._geoidDataList[0].width).toEqual(2); @@ -499,7 +456,7 @@ describe("Scene/I3SDataProvider", function () { }); testProvider._extent = Rectangle.fromDegrees(-1, 0, 1, 2); - return testProvider._loadGeoidData().then(function () { + return testProvider.loadGeoidData().then(function () { expect(testProvider._geoidDataList).toBeUndefined(); }); }); diff --git a/packages/engine/Specs/Scene/I3SLayerSpec.js b/packages/engine/Specs/Scene/I3SLayerSpec.js index 4dbf710091a9..4bc6b24ab3a1 100644 --- a/packages/engine/Specs/Scene/I3SLayerSpec.js +++ b/packages/engine/Specs/Scene/I3SLayerSpec.js @@ -107,7 +107,9 @@ describe("Scene/I3SLayer", function () { const mockI3SProvider = new I3SDataProvider({ url: "mockProviderUrl?testQuery=test", }); - mockI3SProvider._geoidDataIsReadyPromise = Promise.resolve(); + spyOn(I3SDataProvider.prototype, "loadGeoidData").and.returnValue( + Promise.resolve() + ); return mockI3SProvider; } diff --git a/packages/engine/Specs/Scene/I3SNodeSpec.js b/packages/engine/Specs/Scene/I3SNodeSpec.js index 6e6941b374ac..5e7e25beab0a 100644 --- a/packages/engine/Specs/Scene/I3SNodeSpec.js +++ b/packages/engine/Specs/Scene/I3SNodeSpec.js @@ -654,14 +654,12 @@ describe("Scene/I3SNode", function () { it("loads root node from uri", function () { const rootNode = new I3SNode(mockI3SLayerWithoutNodePages, "mockUrl", true); - spyOn(rootNode._dataProvider, "_loadJson").and.returnValue( + const spy = spyOn(I3SDataProvider, "loadJson").and.returnValue( Promise.resolve(rootNodeWithChildren) ); return rootNode.load().then(function () { - expect(rootNode._dataProvider._loadJson).toHaveBeenCalledWith( - rootNode.resource - ); + expect(spy).toHaveBeenCalledWith(rootNode.resource, false); expect(rootNode.data.children.length).toEqual(2); }); }); @@ -669,7 +667,7 @@ describe("Scene/I3SNode", function () { it("loads child node from uri", function () { const rootNode = new I3SNode(mockI3SLayerWithoutNodePages, "mockUrl", true); const childNode = new I3SNode(rootNode, "mockUrlChild", false); - spyOn(childNode._dataProvider, "_loadJson").and.returnValue( + const spy = spyOn(I3SDataProvider, "loadJson").and.returnValue( Promise.resolve(nodeWithContent) ); @@ -679,9 +677,7 @@ describe("Scene/I3SNode", function () { return childNode.load(); }) .then(function () { - expect(childNode._dataProvider._loadJson).toHaveBeenCalledWith( - childNode.resource - ); + expect(spy).toHaveBeenCalledWith(childNode.resource, false); expect(childNode.data.children.length).toEqual(0); expect(childNode.tile).toBeDefined(); expect(childNode.tile.i3sNode).toBe(childNode); @@ -951,7 +947,7 @@ describe("Scene/I3SNode", function () { true ); - spyOn(nodeWithMesh._dataProvider, "_loadJson").and.returnValue( + spyOn(I3SDataProvider, "loadJson").and.returnValue( Promise.resolve(nodeWithContent) ); spyOn(nodeWithMesh._dataProvider, "_loadBinary").and.returnValue( @@ -1070,7 +1066,7 @@ describe("Scene/I3SNode", function () { true ); - spyOn(nodeWithTexturedMesh._dataProvider, "_loadJson").and.returnValue( + spyOn(I3SDataProvider, "loadJson").and.returnValue( Promise.resolve(nodeWithTexturedContent) ); spyOn(nodeWithTexturedMesh._dataProvider, "_loadBinary").and.returnValue( @@ -1131,7 +1127,7 @@ describe("Scene/I3SNode", function () { true ); - spyOn(nodeWithMesh._dataProvider, "_loadJson").and.callFake(function ( + const spy = spyOn(I3SDataProvider, "loadJson").and.callFake(function ( resource ) { if ( @@ -1167,8 +1163,9 @@ describe("Scene/I3SNode", function () { expect(nodeWithMesh.featureData[0].data.featureData).toEqual([]); expect(nodeWithMesh.featureData[0].data.geometryData).toEqual([]); - expect(nodeWithMesh._dataProvider._loadJson).toHaveBeenCalledWith( - nodeWithMesh.featureData[0].resource + expect(spy).toHaveBeenCalledWith( + nodeWithMesh.featureData[0].resource, + false ); }); }); @@ -1188,16 +1185,14 @@ describe("Scene/I3SNode", function () { }); }); - it("load feature data rejects invalid url", function () { + it("load feature data rejects invalid url", async function () { const nodeWithMesh = new I3SNode( mockI3SLayerWithoutNodePages, "mockNodeUrl", true ); - spyOn(nodeWithMesh._dataProvider, "_loadJson").and.callFake(function ( - resource - ) { + spyOn(I3SDataProvider, "loadJson").and.callFake(function (resource) { if ( resource .getUrlComponent() @@ -1218,17 +1213,8 @@ describe("Scene/I3SNode", function () { return Promise.reject(); }); - return nodeWithMesh - .load() - .then(function () { - return nodeWithMesh._loadFeatureData(); - }) - .then(function () { - fail("Promise should not be resolved for invalid uri"); - }) - .catch(function (error) { - expect(error.statusCode).toEqual(404); - }); + await nodeWithMesh.load(); + await expectAsync(nodeWithMesh._loadFeatureData()).toBeRejected(); }); it("creates 3d tile content", function () { @@ -1270,8 +1256,8 @@ describe("Scene/I3SNode", function () { }; spyOn( nodeWithMesh._dataProvider, - "_getDecoderTaskProcessor" - ).and.returnValue(mockProcessor); + "getDecoderTaskProcessor" + ).and.returnValue(Promise.resolve(mockProcessor)); return rootNode .load() @@ -1282,11 +1268,11 @@ describe("Scene/I3SNode", function () { return nodeWithMesh._createContentURL(); }) .then(function (result) { - expect(nodeWithMesh._glbURL).toBeDefined(); + expect(result).toBeDefined(); expect(nodeWithMesh.tile).toBeDefined(); //Test fetching the blob url that was created - return Resource.fetchArrayBuffer(nodeWithMesh._glbURL); + return Resource.fetchArrayBuffer(result); }) .then(function (data) { expect(data.error).toBeUndefined(); @@ -1419,18 +1405,11 @@ describe("Scene/I3SNode", function () { }) .then(function (result) { spyOn(childNode, "_createContentURL").and.callFake(function () { - childNode._glbURL = "mockGlbUrl"; - return Promise.resolve(); + return Promise.resolve("mockGlbUrl"); }); - spyOn(childNode.tile, "_hookedRequestContent").and.callFake( - function () { - childNode.tile._contentReadyToProcessPromise = Promise.resolve(); - } - ); + spyOn(childNode.tile, "_hookedRequestContent"); - childNode.tile._contentResource = { _url: "mockOriginalContentUrl" }; - childNode.tile.requestContent(); - return Promise.all([childNode.tile._contentReadyToProcessPromise]); + return childNode.tile.requestContent(); }) .then(function () { expect(childNode.tile._contentResource._url).toEqual("mockGlbUrl"); diff --git a/packages/engine/Specs/Scene/TimeDynamicPointCloudSpec.js b/packages/engine/Specs/Scene/TimeDynamicPointCloudSpec.js index ef2a07c44499..db47c8490048 100644 --- a/packages/engine/Specs/Scene/TimeDynamicPointCloudSpec.js +++ b/packages/engine/Specs/Scene/TimeDynamicPointCloudSpec.js @@ -828,29 +828,21 @@ describe( }); return loadFrame(pointCloud).then(function () { const decoder = DracoLoader._getDecoderTaskProcessor(); + let frameFailed = false; spyOn(decoder, "scheduleTask").and.callFake(function () { return Promise.reject({ message: "my error" }); }); - const spyUpdate = jasmine.createSpy("listener"); - pointCloud.frameFailed.addEventListener(spyUpdate); + pointCloud.frameFailed.addEventListener((error) => { + frameFailed = true; + expect(error).toBeDefined(); + expect(error.uri).toContain("1.pnts"); + expect(error.message).toBe("my error"); + }); goToFrame(1); scene.renderForSpecs(); - let failedPromise; - let frameFailed = false; return pollToPromise(function () { - const contents = pointCloud._frames[1].pointCloud; - if (defined(contents) && !defined(failedPromise)) { - failedPromise = contents.readyPromise.catch(function () { - frameFailed = true; - }); - } scene.renderForSpecs(); return frameFailed; - }).then(function () { - const arg = spyUpdate.calls.argsFor(0)[0]; - expect(arg).toBeDefined(); - expect(arg.uri).toContain("1.pnts"); - expect(arg.message).toBe("my error"); }); }); }); diff --git a/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js b/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js index 68cc557ab410..971de1e19d6f 100644 --- a/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js +++ b/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js @@ -14,18 +14,17 @@ describe( let scene; let provider; - beforeEach(function () { + beforeEach(async function () { scene = createScene(); const camera = scene.camera; camera.position = Cartesian3.fromElements(-10, -10, -10); camera.direction = Cartesian3.fromElements(1, 1, 1); - provider = new Cesium3DTilesVoxelProvider({ - url: "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json", - }); - - return provider.readyPromise; + provider = await Cesium3DTilesVoxelProvider.fromUrl( + "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json" + ); + return provider; }); afterEach(function () { @@ -44,10 +43,12 @@ describe( it("constructs with options", async function () { const primitive = new VoxelPrimitive({ provider }); scene.primitives.add(primitive); - scene.renderForSpecs(); expect(primitive.provider).toBe(provider); - await primitive.readyPromise; + await pollToPromise(() => { + scene.renderForSpecs(); + return primitive.ready; + }); expect(primitive.shape._type).toBe(provider.shape._type); expect(primitive.dimensions.equals(provider.dimensions)).toBe(true); @@ -134,7 +135,10 @@ describe( const shape = primitive._shape; shape.translation = new Cartesian3(2.382, -3.643, 1.084); - await primitive.readyPromise; + await pollToPromise(() => { + scene.renderForSpecs(); + return primitive.ready; + }); primitive.update(scene.frameState); const stepSizeUv = shape.computeApproximateStepSize(primitive.dimensions); @@ -146,7 +150,10 @@ describe( scene.primitives.add(primitive); scene.renderForSpecs(); - await primitive.readyPromise; + await pollToPromise(() => { + scene.renderForSpecs(); + return primitive.ready; + }); expect(primitive.customShader).toBe(VoxelPrimitive.DefaultCustomShader); @@ -173,7 +180,10 @@ describe( scene.renderForSpecs(); expect(primitive.isDestroyed()).toBe(false); - await primitive.readyPromise; + await pollToPromise(() => { + scene.renderForSpecs(); + return primitive.ready; + }); primitive.update(scene.frameState); expect(primitive.isDestroyed()).toBe(false); diff --git a/packages/engine/Specs/Scene/buildVoxelDrawCommandsSpec.js b/packages/engine/Specs/Scene/buildVoxelDrawCommandsSpec.js index 1835f16b7382..7ba353984ca8 100644 --- a/packages/engine/Specs/Scene/buildVoxelDrawCommandsSpec.js +++ b/packages/engine/Specs/Scene/buildVoxelDrawCommandsSpec.js @@ -12,14 +12,12 @@ describe("Scene/buildVoxelDrawCommands", function () { let scene; let provider; - beforeAll(function () { + beforeAll(async function () { scene = createScene(); - provider = new Cesium3DTilesVoxelProvider({ - url: "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json", - }); - - return provider._readyPromise; + provider = await Cesium3DTilesVoxelProvider.fromUrl( + "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json" + ); }); afterAll(function () { diff --git a/packages/engine/Specs/Scene/processVoxelPropertiesSpec.js b/packages/engine/Specs/Scene/processVoxelPropertiesSpec.js index 808586fe14e3..b885ab52e4fb 100644 --- a/packages/engine/Specs/Scene/processVoxelPropertiesSpec.js +++ b/packages/engine/Specs/Scene/processVoxelPropertiesSpec.js @@ -11,21 +11,19 @@ describe("Scene/processVoxelProperties", function () { let scene; let provider; - beforeAll(function () { + beforeAll(async function () { scene = createScene(); - provider = new Cesium3DTilesVoxelProvider({ - url: "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json", - }); - - return provider.readyPromise; + provider = await Cesium3DTilesVoxelProvider.fromUrl( + "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json" + ); }); afterAll(function () { scene.destroyForSpecs(); }); - it("adds shader defines and structs", function () { + it("adds shader defines and structs", async function () { const primitive = new VoxelPrimitive({ provider }); primitive.update(scene.frameState); diff --git a/packages/widgets/Source/Viewer/Viewer.js b/packages/widgets/Source/Viewer/Viewer.js index 42216d4aa28f..ab404d8a009e 100644 --- a/packages/widgets/Source/Viewer/Viewer.js +++ b/packages/widgets/Source/Viewer/Viewer.js @@ -2233,7 +2233,7 @@ function updateZoomTarget(viewer) { // If zoomTarget was Cesium3DTileset if (target instanceof Cesium3DTileset || target instanceof VoxelPrimitive) { - // This is here for backwards compatibility and can be removed once Cesium3DTileset.readyPromise is removed. + // This is here for backwards compatibility and can be removed once Cesium3DTileset.readyPromise and VoxelPrimitive.readyPromise is removed. return target._readyPromise .then(function () { const boundingSphere = target.boundingSphere; @@ -2277,7 +2277,8 @@ function updateZoomTarget(viewer) { // If zoomTarget was TimeDynamicPointCloud if (target instanceof TimeDynamicPointCloud) { - return target.readyPromise.then(function () { + // This is here for backwards compatibility and can be removed once TimeDynamicPointCloud.readyPromise is removed. + return target._readyPromise.then(function () { const boundingSphere = target.boundingSphere; // If offset was originally undefined then give it base value instead of empty object if (!defined(zoomOptions.offset)) { diff --git a/packages/widgets/Source/VoxelInspector/VoxelInspectorViewModel.js b/packages/widgets/Source/VoxelInspector/VoxelInspectorViewModel.js index af391c97aad7..265ef89468a6 100644 --- a/packages/widgets/Source/VoxelInspector/VoxelInspectorViewModel.js +++ b/packages/widgets/Source/VoxelInspector/VoxelInspectorViewModel.js @@ -720,7 +720,8 @@ Object.defineProperties(VoxelInspectorViewModel.prototype, { this._voxelPrimitive = voxelPrimitive; const that = this; - that._voxelPrimitive.readyPromise.then(function () { + // This is here for backwards compatibility. This can be done immediately once readyPromise is removed. + that._voxelPrimitive._readyPromise.then(function () { that._customShaderCompilationRemoveCallback = that._voxelPrimitive.customShaderCompilationEvent.addEventListener( function (error) { const shaderString = diff --git a/packages/widgets/Specs/Viewer/ViewerSpec.js b/packages/widgets/Specs/Viewer/ViewerSpec.js index cfabcc15a8b3..443d94001a58 100644 --- a/packages/widgets/Specs/Viewer/ViewerSpec.js +++ b/packages/widgets/Specs/Viewer/ViewerSpec.js @@ -1406,14 +1406,14 @@ describe( }); }); - function loadVoxelPrimitive(viewer) { + async function loadVoxelPrimitive(viewer) { const voxelPrimitive = new VoxelPrimitive({ - provider: new Cesium3DTilesVoxelProvider({ - url: "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json", - }), + provider: await Cesium3DTilesVoxelProvider.fromUrl( + "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json" + ), }); viewer.scene.primitives.add(voxelPrimitive); - return voxelPrimitive.readyPromise; + return voxelPrimitive; } it("zoomTo zooms to VoxelPrimitive with default offset when offset not defined", function () { From b14765558c4c71853321c47951349ff7dce493b9 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Mon, 27 Mar 2023 15:28:49 -0400 Subject: [PATCH 14/18] Fix vector tile example --- packages/engine/Source/Scene/Vector3DTileContent.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/engine/Source/Scene/Vector3DTileContent.js b/packages/engine/Source/Scene/Vector3DTileContent.js index 56d7e750b5f0..396e1a9e52fb 100644 --- a/packages/engine/Source/Scene/Vector3DTileContent.js +++ b/packages/engine/Source/Scene/Vector3DTileContent.js @@ -25,7 +25,6 @@ import decodeVectorPolylinePositions from "../Core/decodeVectorPolylinePositions *

    * Implements the {@link Cesium3DTileContent} interface. *

    - * This object is normally not instantiated directly, use {@link Vector3DTileContent.fromTile}. * * @alias Vector3DTileContent * @constructor @@ -298,6 +297,7 @@ function initialize(content, arrayBuffer, byteOffset) { byteOffset += sizeOfUint32; if (byteLength === 0) { + content._ready = true; return; } @@ -622,8 +622,6 @@ function initialize(content, arrayBuffer, byteOffset) { batchTable: batchTable, }); } - - return; } function createFeatures(content) { From fbfcc66caff29cebf640cc1cd0c5df4231c2ee32 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Mon, 27 Mar 2023 15:33:53 -0400 Subject: [PATCH 15/18] Fix docs --- packages/engine/Source/Scene/I3SDataProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/I3SDataProvider.js b/packages/engine/Source/Scene/I3SDataProvider.js index 53e55f94a23c..d284e00bf5cf 100644 --- a/packages/engine/Source/Scene/I3SDataProvider.js +++ b/packages/engine/Source/Scene/I3SDataProvider.js @@ -513,7 +513,7 @@ I3SDataProvider._fetchJson = function (resource) { * @private * * @param {Resource} resource The JSON resource to request - * @param {bool=false} trace Log the resource + * @param {boolean} [trace=false] Log the resource * @returns {Promise} The fetched data */ I3SDataProvider.loadJson = async function (resource, trace) { From dee5f0d18a6237e3d18067b69a1af9ee357dfee1 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Tue, 28 Mar 2023 13:40:22 -0400 Subject: [PATCH 16/18] Cleanup --- CHANGES.md | 2 +- .../Source/DataSources/ModelVisualizer.js | 95 ++++++------ packages/engine/Source/Scene/Cesium3DTile.js | 146 +++++++++--------- .../Source/Scene/Cesium3DTileContent.js | 2 +- .../engine/Source/Scene/Cesium3DTileset.js | 130 +++++++--------- .../Source/Scene/Composite3DTileContent.js | 30 ++-- .../Source/Scene/GltfBufferViewLoader.js | 103 ++++++------ .../engine/Source/Scene/GltfDracoLoader.js | 109 ++++++------- .../Source/Scene/GltfIndexBufferLoader.js | 9 +- packages/engine/Source/Scene/GltfLoader.js | 47 +++--- .../Scene/GltfStructuralMetadataLoader.js | 56 +++---- .../engine/Source/Scene/GltfTextureLoader.js | 69 +++++---- .../engine/Source/Scene/I3SDataProvider.js | 59 +++---- .../engine/Source/Scene/Model/ModelUtility.js | 6 +- .../Source/Scene/Multiple3DTileContent.js | 131 +++++++--------- .../Specs/Scene/Empty3DTileContentSpec.js | 1 - packages/engine/Specs/Scene/GltfLoaderSpec.js | 28 ---- .../Scene/GroundPolylinePrimitiveSpec.js | 47 +++--- .../engine/Specs/Scene/GroundPrimitiveSpec.js | 68 ++++---- .../Scene/Model/loadAndZoomToModelAsync.js | 45 +----- packages/widgets/Specs/Viewer/ViewerSpec.js | 8 +- 21 files changed, 566 insertions(+), 625 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7cdb5a5e1917..4c935d4b0b5f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,7 +19,7 @@ try { ##### Additions :tada: -- Added `ArcGisMapServerImageryProvider.fromUrl`, `ArcGISTiledElevationTerrainProvider.fromUrl`, `BingMapsImageryProvider.fromUrl`, `CesiumTerrainProvider.fromUrl`, `GoogleEarthEnterpriseMetadata.fromUrl`, `GoogleEarthEnterpriseImageryProvider.fromMetadata`, `GoogleEarthEnterpriseMapsProvider.fromUrl`, `GoogleEarthEnterpriseTerrainProvider.fromMetadata`, `ImageryLayer.fromProviderAsync`, `IonImageryProvider.fromAssetId`, `SingleTileImageryProvider.fromUrl`, `Terrain`, `TileMapServiceImageryProvider.fromUrl`, `VRTheWorldTerrainProvider.fromUrl`, `createWorldTerrainAsync`, `Cesium3DTileset.fromUrl`, `Cesium3DTileset.fromIonAssetId`, `createOsmBuildingsAsync`, `Model.fromGltfAsync`, `Model.readyEvent`, `Model.errorEvent`, and `Model.texturesReadyEvent` for better async flow and error handling. +- Added `ArcGisMapServerImageryProvider.fromUrl`, `ArcGISTiledElevationTerrainProvider.fromUrl`, `BingMapsImageryProvider.fromUrl`, `CesiumTerrainProvider.fromUrl`, `GoogleEarthEnterpriseMetadata.fromUrl`, `GoogleEarthEnterpriseImageryProvider.fromMetadata`, `GoogleEarthEnterpriseMapsProvider.fromUrl`, `GoogleEarthEnterpriseTerrainProvider.fromMetadata`, `ImageryLayer.fromProviderAsync`, `IonImageryProvider.fromAssetId`, `SingleTileImageryProvider.fromUrl`, `Terrain`, `TileMapServiceImageryProvider.fromUrl`, `VRTheWorldTerrainProvider.fromUrl`, `createWorldTerrainAsync`, `Cesium3DTileset.fromUrl`, `Cesium3DTileset.fromIonAssetId`, `createOsmBuildingsAsync`, `Model.fromGltfAsync`, `Model.readyEvent`, `Model.errorEvent`,`Model.texturesReadyEvent`, `I3SDataProvider.fromUrl`, and `Cesium3DTilesVoxelProvider.fromUrl` for better async flow and error handling. ##### Fixes :wrench: diff --git a/packages/engine/Source/DataSources/ModelVisualizer.js b/packages/engine/Source/DataSources/ModelVisualizer.js index 337e8012822b..1297ad74707d 100644 --- a/packages/engine/Source/DataSources/ModelVisualizer.js +++ b/packages/engine/Source/DataSources/ModelVisualizer.js @@ -66,6 +66,52 @@ function ModelVisualizer(scene, entityCollection) { this._onCollectionChanged(entityCollection, entityCollection.values, [], []); } +async function createModelPrimitive( + visualizer, + entity, + resource, + incrementallyLoadTextures +) { + const primitives = visualizer._primitives; + const modelHash = visualizer._modelHash; + + try { + const model = await Model.fromGltfAsync({ + url: resource, + incrementallyLoadTextures: incrementallyLoadTextures, + scene: visualizer._scene, + }); + + if (visualizer.isDestroyed() || !defined(modelHash[entity.id])) { + return; + } + + model.id = entity; + primitives.add(model); + modelHash[entity.id].modelPrimitive = model; + model.errorEvent.addEventListener((error) => { + if (!defined(modelHash[entity.id])) { + return; + } + + console.log(error); + + // Texture failures when incrementallyLoadTextures + // will not affect the ability to compute the bounding sphere + if (error.name !== "TextureError" && model.incrementallyLoadTextures) { + modelHash[entity.id].loadFailed = true; + } + }); + } catch (error) { + if (visualizer.isDestroyed() || !defined(modelHash[entity.id])) { + return; + } + + console.log(error); + modelHash[entity.id].loadFailed = true; + } +} + /** * Updates models created this visualizer to match their * Entity counterpart at the given time. @@ -131,50 +177,13 @@ ModelVisualizer.prototype.update = function (time) { }; modelHash[entity.id] = modelData; - (async () => { - try { - const model = await Model.fromGltfAsync({ - url: resource, - incrementallyLoadTextures: Property.getValueOrDefault( - modelGraphics._incrementallyLoadTextures, - time, - defaultIncrementallyLoadTextures - ), - scene: this._scene, - }); - - if (this.isDestroyed() || !defined(modelHash[entity.id])) { - return; - } - - model.id = entity; - primitives.add(model); - modelHash[entity.id].modelPrimitive = model; - model.errorEvent.addEventListener((error) => { - if (!defined(modelHash[entity.id])) { - return; - } - - console.log(error); - - // Texture failures when incrementallyLoadTextures - // will not affect the ability to compute the bounding sphere - if ( - error.name !== "TextureError" && - model.incrementallyLoadTextures - ) { - modelHash[entity.id].loadFailed = true; - } - }); - } catch (error) { - if (this.isDestroyed() || !defined(modelHash[entity.id])) { - return; - } + const incrementallyLoadTextures = Property.getValueOrDefault( + modelGraphics._incrementallyLoadTextures, + time, + defaultIncrementallyLoadTextures + ); - console.log(error); - modelHash[entity.id].loadFailed = true; - } - })(); + createModelPrimitive(this, entity, resource, incrementallyLoadTextures); } const model = modelData.modelPrimitive; diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js index 30087fff82ad..eb056f4d7062 100644 --- a/packages/engine/Source/Scene/Cesium3DTile.js +++ b/packages/engine/Source/Scene/Cesium3DTile.js @@ -1105,7 +1105,7 @@ function requestMultipleContents(tile) { tile._contentState = Cesium3DTileContentState.LOADING; return promise - .then(async (content) => { + .then((content) => { if (tile.isDestroyed()) { // Tile is unloaded before the content can process return; @@ -1130,6 +1130,81 @@ function requestMultipleContents(tile) { }); } +async function processArrayBuffer( + tile, + tileset, + request, + expired, + requestPromise +) { + const previousState = tile._contentState; + tile._contentState = Cesium3DTileContentState.LOADING; + ++tileset.statistics.numberOfPendingRequests; + + let arrayBuffer; + try { + arrayBuffer = await requestPromise; + } catch (error) { + --tileset.statistics.numberOfPendingRequests; + if (tile.isDestroyed()) { + // Tile is unloaded before the content can process + return; + } + + if (request.cancelled || request.state === RequestState.CANCELLED) { + // Cancelled due to low priority - try again later. + tile._contentState = previousState; + ++tileset.statistics.numberOfAttemptedRequests; + return; + } + + tile._contentState = Cesium3DTileContentState.FAILED; + throw error; + } + + if (tile.isDestroyed()) { + --tileset.statistics.numberOfPendingRequests; + // Tile is unloaded before the content can process + return; + } + + if (request.cancelled || request.state === RequestState.CANCELLED) { + // Cancelled due to low priority - try again later. + tile._contentState = previousState; + --tileset.statistics.numberOfPendingRequests; + ++tileset.statistics.numberOfAttemptedRequests; + return; + } + + try { + const content = await makeContent(tile, arrayBuffer); + --tileset.statistics.numberOfPendingRequests; + + if (tile.isDestroyed()) { + // Tile is unloaded before the content can process + return; + } + + if (expired) { + tile.expireDate = undefined; + } + + tile._content = content; + tile._contentState = Cesium3DTileContentState.PROCESSING; + + return content; + } catch (error) { + --tileset.statistics.numberOfPendingRequests; + if (tile.isDestroyed()) { + // Tile is unloaded before the content can process + return; + } + + tile._contentState = Cesium3DTileContentState.FAILED; + throw error; + } +} + /** * @private * @param {Cesium3DTile} tile @@ -1164,74 +1239,7 @@ function requestSingleContent(tile) { return; } - const previousState = tile._contentState; - tile._contentState = Cesium3DTileContentState.LOADING; - ++tileset.statistics.numberOfPendingRequests; - - return (async () => { - let arrayBuffer; - try { - arrayBuffer = await promise; - } catch (error) { - --tileset.statistics.numberOfPendingRequests; - if (tile.isDestroyed()) { - // Tile is unloaded before the content can process - return; - } - - if (request.cancelled || request.state === RequestState.CANCELLED) { - // Cancelled due to low priority - try again later. - tile._contentState = previousState; - ++tileset.statistics.numberOfAttemptedRequests; - return; - } - - tile._contentState = Cesium3DTileContentState.FAILED; - throw error; - } - - if (tile.isDestroyed()) { - --tileset.statistics.numberOfPendingRequests; - // Tile is unloaded before the content can process - return; - } - - if (request.cancelled || request.state === RequestState.CANCELLED) { - // Cancelled due to low priority - try again later. - tile._contentState = previousState; - --tileset.statistics.numberOfPendingRequests; - ++tileset.statistics.numberOfAttemptedRequests; - return; - } - - try { - const content = await makeContent(tile, arrayBuffer); - --tileset.statistics.numberOfPendingRequests; - - if (tile.isDestroyed()) { - // Tile is unloaded before the content can process - return; - } - - if (expired) { - tile.expireDate = undefined; - } - - tile._content = content; - tile._contentState = Cesium3DTileContentState.PROCESSING; - - return content; - } catch (error) { - --tileset.statistics.numberOfPendingRequests; - if (tile.isDestroyed()) { - // Tile is unloaded before the content can process - return; - } - - tile._contentState = Cesium3DTileContentState.FAILED; - throw error; - } - })(); + return processArrayBuffer(tile, tileset, request, expired, promise); } /** diff --git a/packages/engine/Source/Scene/Cesium3DTileContent.js b/packages/engine/Source/Scene/Cesium3DTileContent.js index 714cd0adc3e3..ba6222f28660 100644 --- a/packages/engine/Source/Scene/Cesium3DTileContent.js +++ b/packages/engine/Source/Scene/Cesium3DTileContent.js @@ -161,7 +161,7 @@ Object.defineProperties(Cesium3DTileContent.prototype, { }, /** - * Returns true when the tile's content is ready to render; otherwise false + * Gets the promise that will be resolved when the tile's content is ready to render. * * @memberof Cesium3DTileContent.prototype * diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 0f34df03d51c..7ddd6a3da9a2 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -58,65 +58,65 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * * Initialization options for the Cesium3DTileset constructor * - * @property {Resource|string|Promise|Promise} [options.url] The url to a tileset JSON file. Deprecated. - * @property {boolean} [options.show=true] Determines if the tileset will be shown. - * @property {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the tileset's root tile. - * @property {Axis} [options.modelUpAxis=Axis.Y] Which axis is considered up when loading models for tile contents. - * @property {Axis} [options.modelForwardAxis=Axis.X] Which axis is considered forward when loading models for tile contents. - * @property {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the tileset casts or receives shadows from light sources. - * @property {number} [options.maximumScreenSpaceError=16] The maximum screen space error used to drive level of detail refinement. - * @property {number} [options.maximumMemoryUsage=512] The maximum amount of memory in MB that can be used by the tileset. - * @property {boolean} [options.cullWithChildrenBounds=true] Optimization option. Whether to cull tiles using the union of their children bounding volumes. - * @property {boolean} [options.cullRequestsWhileMoving=true] Optimization option. Don't request tiles that will likely be unused when they come back because of the camera's movement. This optimization only applies to stationary tilesets. - * @property {number} [options.cullRequestsWhileMovingMultiplier=60.0] Optimization option. Multiplier used in culling requests while moving. Larger is more aggressive culling, smaller less aggressive culling. - * @property {boolean} [options.preloadWhenHidden=false] Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. - * @property {boolean} [options.preloadFlightDestinations=true] Optimization option. Preload tiles at the camera's flight destination while the camera is in flight. - * @property {boolean} [options.preferLeaves=false] Optimization option. Prefer loading of leaves first. - * @property {boolean} [options.dynamicScreenSpaceError=false] Optimization option. Reduce the screen space error for tiles that are further away from the camera. - * @property {number} [options.dynamicScreenSpaceErrorDensity=0.00278] Density used to adjust the dynamic screen space error, similar to fog density. - * @property {number} [options.dynamicScreenSpaceErrorFactor=4.0] A factor used to increase the computed dynamic screen space error. - * @property {number} [options.dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height at which the density starts to falloff. - * @property {number} [options.progressiveResolutionHeightFraction=0.3] Optimization option. If between (0.0, 0.5], tiles at or above the screen space error for the reduced screen resolution of progressiveResolutionHeightFraction*screenHeight will be prioritized first. This can help get a quick layer of tiles down while full resolution tiles continue to load. - * @property {boolean} [options.foveatedScreenSpaceError=true] Optimization option. Prioritize loading tiles in the center of the screen by temporarily raising the screen space error for tiles around the edge of the screen. Screen space error returns to normal once all the tiles in the center of the screen as determined by the {@link Cesium3DTileset#foveatedConeSize} are loaded. - * @property {number} [options.foveatedConeSize=0.1] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the cone size that determines which tiles are deferred. Tiles that are inside this cone are loaded immediately. Tiles outside the cone are potentially deferred based on how far outside the cone they are and their screen space error. This is controlled by {@link Cesium3DTileset#foveatedInterpolationCallback} and {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation}. Setting this to 0.0 means the cone will be the line formed by the camera position and its view direction. Setting this to 1.0 means the cone encompasses the entire field of view of the camera, disabling the effect. - * @property {number} [options.foveatedMinimumScreenSpaceErrorRelaxation=0.0] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the starting screen space error relaxation for tiles outside the foveated cone. The screen space error will be raised starting with tileset value up to {@link Cesium3DTileset#maximumScreenSpaceError} based on the provided {@link Cesium3DTileset#foveatedInterpolationCallback}. - * @property {Cesium3DTileset.foveatedInterpolationCallback} [options.foveatedInterpolationCallback=Math.lerp] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how much to raise the screen space error for tiles outside the foveated cone, interpolating between {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation} and {@link Cesium3DTileset#maximumScreenSpaceError} - * @property {number} [options.foveatedTimeDelay=0.2] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how long in seconds to wait after the camera stops moving before deferred tiles start loading in. This time delay prevents requesting tiles around the edges of the screen when the camera is moving. Setting this to 0.0 will immediately request all tiles in any given view. - * @property {boolean} [options.skipLevelOfDetail=false] Optimization option. Determines if level of detail skipping should be applied during the traversal. - * @property {number} [options.baseScreenSpaceError=1024] When skipLevelOfDetail is true, the screen space error that must be reached before skipping levels of detail. - * @property {number} [options.skipScreenSpaceErrorFactor=16] When skipLevelOfDetail is true, a multiplier defining the minimum screen space error to skip. Used in conjunction with skipLevels to determine which tiles to load. - * @property {number} [options.skipLevels=1] When skipLevelOfDetail is true, a constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. Used in conjunction with skipScreenSpaceErrorFactor to determine which tiles to load. - * @property {boolean} [options.immediatelyLoadDesiredLevelOfDetail=false] When skipLevelOfDetail is true, only tiles that meet the maximum screen space error will ever be downloaded. Skipping factors are ignored and just the desired tiles are loaded. - * @property {boolean} [options.loadSiblings=false] When skipLevelOfDetail is true, determines whether siblings of visible tiles are always downloaded during traversal. - * @property {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset. - * @property {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this tileset. See {@link Cesium3DTileset#classificationType} for details about restrictions and limitations. - * @property {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid determining the size and shape of the globe. - * @property {object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. - * @property {Cartesian3} [options.lightColor] The light color when shading models. When undefined the scene's light color is used instead. - * @property {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting for this tileset. - * @property {boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled. - * @property {boolean} [options.enableShowOutline=true] Whether to enable outlines for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. This can be set to false to avoid the additional processing of geometry at load time. When false, the showOutlines and outlineColor options are ignored. - * @property {boolean} [options.showOutline=true] Whether to display the outline for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. When true, outlines are displayed. When false, outlines are not displayed. - * @property {Color} [options.outlineColor=Color.BLACK] The color to use when rendering outlines. - * @property {boolean} [options.vectorClassificationOnly=false] Indicates that only the tileset's vector tiles should be used for classification. - * @property {boolean} [options.vectorKeepDecodedPositions=false] Whether vector tiles should keep decoded positions in memory. This is used with {@link Cesium3DTileFeature.getPolylinePositions}. - * @property {string|number} [options.featureIdLabel="featureId_0"] Label of the feature ID set to use for picking and styling. For EXT_mesh_features, this is the feature ID's label property, or "featureId_N" (where N is the index in the featureIds array) when not specified. EXT_feature_metadata did not have a label field, so such feature ID sets are always labeled "featureId_N" where N is the index in the list of all feature Ids, where feature ID attributes are listed before feature ID textures. If featureIdLabel is an integer N, it is converted to the string "featureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. - * @property {string|number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. - * @property {boolean} [options.showCreditsOnScreen=false] Whether to display the credits of this tileset on screen. - * @property {SplitDirection} [options.splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this tileset. - * @property {boolean} [options.projectTo2D=false] Whether to accurately project the tileset to 2D. If this is true, the tileset will be projected accurately to 2D, but it will use more memory to do so. If this is false, the tileset will use less memory and will still render in 2D / CV mode, but its projected positions may be inaccurate. This cannot be set after the tileset has loaded. - * @property {string} [options.debugHeatmapTilePropertyName] The tile variable to colorize as a heatmap. All rendered tiles will be colorized relative to each other's specified variable value. - * @property {boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. - * @property {boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. - * @property {boolean} [options.enableDebugWireframe] For debugging only. This must be true for debugWireframe to work in WebGL1. This cannot be set after the tileset has loaded. - * @property {boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. - * @property {boolean} [options.debugShowBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile. - * @property {boolean} [options.debugShowContentBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile's content. - * @property {boolean} [options.debugShowViewerRequestVolume=false] For debugging only. When true, renders the viewer request volume for each tile. - * @property {boolean} [options.debugShowGeometricError=false] For debugging only. When true, draws labels to indicate the geometric error of each tile. - * @property {boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. - * @property {boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. - * @property {boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. + * @property {Resource|string|Promise|Promise} [.url] The url to a tileset JSON file. Deprecated. + * @property {boolean} [show=true] Determines if the tileset will be shown. + * @property {Matrix4} [modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the tileset's root tile. + * @property {Axis} [modelUpAxis=Axis.Y] Which axis is considered up when loading models for tile contents. + * @property {Axis} [modelForwardAxis=Axis.X] Which axis is considered forward when loading models for tile contents. + * @property {ShadowMode} [shadows=ShadowMode.ENABLED] Determines whether the tileset casts or receives shadows from light sources. + * @property {number} [maximumScreenSpaceError=16] The maximum screen space error used to drive level of detail refinement. + * @property {number} [maximumMemoryUsage=512] The maximum amount of memory in MB that can be used by the tileset. + * @property {boolean} [cullWithChildrenBounds=true] Optimization option. Whether to cull tiles using the union of their children bounding volumes. + * @property {boolean} [cullRequestsWhileMoving=true] Optimization option. Don't request tiles that will likely be unused when they come back because of the camera's movement. This optimization only applies to stationary tilesets. + * @property {number} [cullRequestsWhileMovingMultiplier=60.0] Optimization option. Multiplier used in culling requests while moving. Larger is more aggressive culling, smaller less aggressive culling. + * @property {boolean} [preloadWhenHidden=false] Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. + * @property {boolean} [preloadFlightDestinations=true] Optimization option. Preload tiles at the camera's flight destination while the camera is in flight. + * @property {boolean} [preferLeaves=false] Optimization option. Prefer loading of leaves first. + * @property {boolean} [dynamicScreenSpaceError=false] Optimization option. Reduce the screen space error for tiles that are further away from the camera. + * @property {number} [dynamicScreenSpaceErrorDensity=0.00278] Density used to adjust the dynamic screen space error, similar to fog density. + * @property {number} [dynamicScreenSpaceErrorFactor=4.0] A factor used to increase the computed dynamic screen space error. + * @property {number} [dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height at which the density starts to falloff. + * @property {number} [progressiveResolutionHeightFraction=0.3] Optimization option. If between (0.0, 0.5], tiles at or above the screen space error for the reduced screen resolution of progressiveResolutionHeightFraction*screenHeight will be prioritized first. This can help get a quick layer of tiles down while full resolution tiles continue to load. + * @property {boolean} [foveatedScreenSpaceError=true] Optimization option. Prioritize loading tiles in the center of the screen by temporarily raising the screen space error for tiles around the edge of the screen. Screen space error returns to normal once all the tiles in the center of the screen as determined by the {@link Cesium3DTileset#foveatedConeSize} are loaded. + * @property {number} [foveatedConeSize=0.1] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the cone size that determines which tiles are deferred. Tiles that are inside this cone are loaded immediately. Tiles outside the cone are potentially deferred based on how far outside the cone they are and their screen space error. This is controlled by {@link Cesium3DTileset#foveatedInterpolationCallback} and {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation}. Setting this to 0.0 means the cone will be the line formed by the camera position and its view direction. Setting this to 1.0 means the cone encompasses the entire field of view of the camera, disabling the effect. + * @property {number} [foveatedMinimumScreenSpaceErrorRelaxation=0.0] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the starting screen space error relaxation for tiles outside the foveated cone. The screen space error will be raised starting with tileset value up to {@link Cesium3DTileset#maximumScreenSpaceError} based on the provided {@link Cesium3DTileset#foveatedInterpolationCallback}. + * @property {Cesium3DTileset.foveatedInterpolationCallback} [foveatedInterpolationCallback=Math.lerp] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how much to raise the screen space error for tiles outside the foveated cone, interpolating between {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation} and {@link Cesium3DTileset#maximumScreenSpaceError} + * @property {number} [foveatedTimeDelay=0.2] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how long in seconds to wait after the camera stops moving before deferred tiles start loading in. This time delay prevents requesting tiles around the edges of the screen when the camera is moving. Setting this to 0.0 will immediately request all tiles in any given view. + * @property {boolean} [skipLevelOfDetail=false] Optimization option. Determines if level of detail skipping should be applied during the traversal. + * @property {number} [baseScreenSpaceError=1024] When skipLevelOfDetail is true, the screen space error that must be reached before skipping levels of detail. + * @property {number} [skipScreenSpaceErrorFactor=16] When skipLevelOfDetail is true, a multiplier defining the minimum screen space error to skip. Used in conjunction with skipLevels to determine which tiles to load. + * @property {number} [skipLevels=1] When skipLevelOfDetail is true, a constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. Used in conjunction with skipScreenSpaceErrorFactor to determine which tiles to load. + * @property {boolean} [immediatelyLoadDesiredLevelOfDetail=false] When skipLevelOfDetail is true, only tiles that meet the maximum screen space error will ever be downloaded. Skipping factors are ignored and just the desired tiles are loaded. + * @property {boolean} [loadSiblings=false] When skipLevelOfDetail is true, determines whether siblings of visible tiles are always downloaded during traversal. + * @property {ClippingPlaneCollection} [clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset. + * @property {ClassificationType} [classificationType] Determines whether terrain, 3D Tiles or both will be classified by this tileset. See {@link Cesium3DTileset#classificationType} for details about restrictions and limitations. + * @property {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid determining the size and shape of the globe. + * @property {object} [pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. + * @property {Cartesian3} [lightColor] The light color when shading models. When undefined the scene's light color is used instead. + * @property {ImageBasedLighting} [imageBasedLighting] The properties for managing image-based lighting for this tileset. + * @property {boolean} [backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled. + * @property {boolean} [enableShowOutline=true] Whether to enable outlines for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. This can be set to false to avoid the additional processing of geometry at load time. When false, the showOutlines and outlineColor options are ignored. + * @property {boolean} [showOutline=true] Whether to display the outline for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. When true, outlines are displayed. When false, outlines are not displayed. + * @property {Color} [outlineColor=Color.BLACK] The color to use when rendering outlines. + * @property {boolean} [vectorClassificationOnly=false] Indicates that only the tileset's vector tiles should be used for classification. + * @property {boolean} [vectorKeepDecodedPositions=false] Whether vector tiles should keep decoded positions in memory. This is used with {@link Cesium3DTileFeature.getPolylinePositions}. + * @property {string|number} [featureIdLabel="featureId_0"] Label of the feature ID set to use for picking and styling. For EXT_mesh_features, this is the feature ID's label property, or "featureId_N" (where N is the index in the featureIds array) when not specified. EXT_feature_metadata did not have a label field, so such feature ID sets are always labeled "featureId_N" where N is the index in the list of all feature Ids, where feature ID attributes are listed before feature ID textures. If featureIdLabel is an integer N, it is converted to the string "featureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. + * @property {string|number} [instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. + * @property {boolean} [showCreditsOnScreen=false] Whether to display the credits of this tileset on screen. + * @property {SplitDirection} [splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this tileset. + * @property {boolean} [projectTo2D=false] Whether to accurately project the tileset to 2D. If this is true, the tileset will be projected accurately to 2D, but it will use more memory to do so. If this is false, the tileset will use less memory and will still render in 2D / CV mode, but its projected positions may be inaccurate. This cannot be set after the tileset has loaded. + * @property {string} [debugHeatmapTilePropertyName] The tile variable to colorize as a heatmap. All rendered tiles will be colorized relative to each other's specified variable value. + * @property {boolean} [debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. + * @property {boolean} [debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. + * @property {boolean} [enableDebugWireframe] For debugging only. This must be true for debugWireframe to work in WebGL1. This cannot be set after the tileset has loaded. + * @property {boolean} [debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. + * @property {boolean} [debugShowBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile. + * @property {boolean} [debugShowContentBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile's content. + * @property {boolean} [debugShowViewerRequestVolume=false] For debugging only. When true, renders the viewer request volume for each tile. + * @property {boolean} [debugShowGeometricError=false] For debugging only. When true, draws labels to indicate the geometric error of each tile. + * @property {boolean} [debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. + * @property {boolean} [debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. + * @property {boolean} [debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. */ /** @@ -2620,16 +2620,6 @@ function handleTileFailure(error, tileset, tile) { } } -/** - * @private - * @param {Cesium3DTileset} tileset - * @param {Cesium3DTile} tile - * @returns {Function} - */ -function handleTileSuccess(tileset, tile) { - tileset.tileLoad.raiseEvent(tile); -} - /** * @private * @param {Cesium3DTileset} tileset @@ -2672,7 +2662,7 @@ function processTiles(tileset, frameState) { if (tile.contentReady) { --statistics.numberOfTilesProcessing; - handleTileSuccess(tileset, tile, tile.content); + tileset.tileLoad.raiseEvent(tile); } } catch (error) { --statistics.numberOfTilesProcessing; diff --git a/packages/engine/Source/Scene/Composite3DTileContent.js b/packages/engine/Source/Scene/Composite3DTileContent.js index 0c224a38cfad..e61d10e819ac 100644 --- a/packages/engine/Source/Scene/Composite3DTileContent.js +++ b/packages/engine/Source/Scene/Composite3DTileContent.js @@ -17,11 +17,15 @@ import RuntimeError from "../Core/RuntimeError.js"; * * @private */ -function Composite3DTileContent(tileset, tile, resource) { +function Composite3DTileContent(tileset, tile, resource, contents) { this._tileset = tileset; this._tile = tile; this._resource = resource; - this._contents = []; + + if (!defined(contents)) { + contents = []; + } + this._contents = contents; this._metadata = undefined; this._group = undefined; @@ -230,8 +234,6 @@ Composite3DTileContent.fromTileType = async function ( const tilesLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; - const content = new Composite3DTileContent(tileset, tile, resource); - // For caching purposes, models within the composite tile must be // distinguished. To do this, add a query parameter ?compositeIndex=i. // Since composite tiles may contain other composite tiles, check for an @@ -246,6 +248,8 @@ Composite3DTileContent.fromTileType = async function ( prefix = ""; } + const promises = []; + promises.length = tilesLength; for (let i = 0; i < tilesLength; ++i) { const tileType = getMagic(uint8Array, byteOffset); @@ -263,16 +267,9 @@ Composite3DTileContent.fromTileType = async function ( }); if (defined(contentFactory)) { - const innerContent = await Promise.resolve( - contentFactory( - content._tileset, - content._tile, - childResource, - arrayBuffer, - byteOffset - ) + promises[i] = Promise.resolve( + contentFactory(tileset, tile, childResource, arrayBuffer, byteOffset) ); - content._contents.push(innerContent); } else { throw new RuntimeError( `Unknown tile content type, ${tileType}, inside Composite tile` @@ -282,6 +279,13 @@ Composite3DTileContent.fromTileType = async function ( byteOffset += tileByteLength; } + const innerContents = await Promise.all(promises); + const content = new Composite3DTileContent( + tileset, + tile, + resource, + innerContents + ); return content; }; diff --git a/packages/engine/Source/Scene/GltfBufferViewLoader.js b/packages/engine/Source/Scene/GltfBufferViewLoader.js index a0bb01d6d378..714d3166bae7 100644 --- a/packages/engine/Source/Scene/GltfBufferViewLoader.js +++ b/packages/engine/Source/Scene/GltfBufferViewLoader.js @@ -125,6 +125,56 @@ Object.defineProperties(GltfBufferViewLoader.prototype, { }, }); +async function loadResources(loader) { + try { + const bufferLoader = getBufferLoader(loader); + loader._bufferLoader = bufferLoader; + await bufferLoader.load(); + + if (loader.isDestroyed()) { + return; + } + + const bufferTypedArray = bufferLoader.typedArray; + const bufferViewTypedArray = new Uint8Array( + bufferTypedArray.buffer, + bufferTypedArray.byteOffset + loader._byteOffset, + loader._byteLength + ); + + // Unload the buffer + loader.unload(); + + loader._typedArray = bufferViewTypedArray; + if (loader._hasMeshopt) { + const count = loader._meshoptCount; + const byteStride = loader._meshoptByteStride; + const result = new Uint8Array(count * byteStride); + MeshoptDecoder.decodeGltfBuffer( + result, + count, + byteStride, + loader._typedArray, + loader._meshoptMode, + loader._meshoptFilter + ); + loader._typedArray = result; + } + + loader._state = ResourceLoaderState.READY; + return loader; + } catch (error) { + if (loader.isDestroyed()) { + return; + } + + loader.unload(); + loader._state = ResourceLoaderState.FAILED; + const errorMessage = "Failed to load buffer view"; + throw loader.getError(errorMessage, error); + } +} + /** * Loads the resource. * @returns {Promise} A promise which resolves to the loader when the resource loading is completed. @@ -135,57 +185,8 @@ GltfBufferViewLoader.prototype.load = async function () { return this._promise; } - this._promise = (async () => { - this._state = ResourceLoaderState.LOADING; - try { - const bufferLoader = getBufferLoader(this); - this._bufferLoader = bufferLoader; - await bufferLoader.load(); - - if (this.isDestroyed()) { - return; - } - - const bufferTypedArray = bufferLoader.typedArray; - const bufferViewTypedArray = new Uint8Array( - bufferTypedArray.buffer, - bufferTypedArray.byteOffset + this._byteOffset, - this._byteLength - ); - - // Unload the buffer - this.unload(); - - this._typedArray = bufferViewTypedArray; - if (this._hasMeshopt) { - const count = this._meshoptCount; - const byteStride = this._meshoptByteStride; - const result = new Uint8Array(count * byteStride); - MeshoptDecoder.decodeGltfBuffer( - result, - count, - byteStride, - this._typedArray, - this._meshoptMode, - this._meshoptFilter - ); - this._typedArray = result; - } - - this._state = ResourceLoaderState.READY; - return this; - } catch (error) { - if (this.isDestroyed()) { - return; - } - - this.unload(); - this._state = ResourceLoaderState.FAILED; - const errorMessage = "Failed to load buffer view"; - throw this.getError(errorMessage, error); - } - })(); - + this._state = ResourceLoaderState.LOADING; + this._promise = loadResources(this); return this._promise; }; diff --git a/packages/engine/Source/Scene/GltfDracoLoader.js b/packages/engine/Source/Scene/GltfDracoLoader.js index 18648d869fc9..a1902e0ba848 100644 --- a/packages/engine/Source/Scene/GltfDracoLoader.js +++ b/packages/engine/Source/Scene/GltfDracoLoader.js @@ -93,6 +93,34 @@ Object.defineProperties(GltfDracoLoader.prototype, { }, }); +async function loadResources(loader) { + const resourceCache = loader._resourceCache; + try { + const bufferViewLoader = resourceCache.getBufferViewLoader({ + gltf: loader._gltf, + bufferViewId: loader._draco.bufferView, + gltfResource: loader._gltfResource, + baseResource: loader._baseResource, + }); + loader._bufferViewLoader = bufferViewLoader; + await bufferViewLoader.load(); + + if (loader.isDestroyed()) { + return; + } + + loader._bufferViewTypedArray = bufferViewLoader.typedArray; + loader._state = ResourceLoaderState.PROCESSING; + return loader; + } catch (error) { + if (loader.isDestroyed()) { + return; + } + + handleError(loader, error); + } +} + /** * Loads the resource. * @returns {Promise} A promise which resolves to the loader when the resource loading is completed. @@ -104,34 +132,7 @@ GltfDracoLoader.prototype.load = async function () { } this._state = ResourceLoaderState.LOADING; - const resourceCache = this._resourceCache; - this._promise = (async () => { - try { - const bufferViewLoader = resourceCache.getBufferViewLoader({ - gltf: this._gltf, - bufferViewId: this._draco.bufferView, - gltfResource: this._gltfResource, - baseResource: this._baseResource, - }); - this._bufferViewLoader = bufferViewLoader; - await bufferViewLoader.load(); - - if (this.isDestroyed()) { - return; - } - - this._bufferViewTypedArray = bufferViewLoader.typedArray; - this._state = ResourceLoaderState.PROCESSING; - return this; - } catch (error) { - if (this.isDestroyed()) { - return; - } - - handleError(this, error); - } - })(); - + this._promise = loadResources(this); return this._promise; }; @@ -142,6 +143,32 @@ function handleError(dracoLoader, error) { throw dracoLoader.getError(errorMessage, error); } +async function processDecode(loader, decodePromise) { + try { + const results = await decodePromise; + if (loader.isDestroyed()) { + return; + } + + // Unload everything except the decoded data + loader.unload(); + + loader._decodedData = { + indices: results.indexArray, + vertexAttributes: results.attributeData, + }; + loader._state = ResourceLoaderState.READY; + return loader._baseResource; + } catch (error) { + if (loader.isDestroyed()) { + return; + } + + // Capture this error so it can be thrown on the next `process` call + loader._dracoError = error; + } +} + /** * Processes the resource until it becomes ready. * @@ -200,31 +227,7 @@ GltfDracoLoader.prototype.process = function (frameState) { return false; } - this._decodePromise = (async () => { - try { - const results = await decodePromise; - if (this.isDestroyed()) { - return; - } - - // Unload everything except the decoded data - this.unload(); - - this._decodedData = { - indices: results.indexArray, - vertexAttributes: results.attributeData, - }; - this._state = ResourceLoaderState.READY; - return this._baseResource; - } catch (error) { - if (this.isDestroyed()) { - return; - } - - // Capture this error so it can be thrown on the next `process` call - this._dracoError = error; - } - })(); + this._decodePromise = processDecode(this, decodePromise); }; /** diff --git a/packages/engine/Source/Scene/GltfIndexBufferLoader.js b/packages/engine/Source/Scene/GltfIndexBufferLoader.js index ca6ace4e0533..de52c8412095 100644 --- a/packages/engine/Source/Scene/GltfIndexBufferLoader.js +++ b/packages/engine/Source/Scene/GltfIndexBufferLoader.js @@ -335,18 +335,19 @@ GltfIndexBufferLoader.prototype.process = function (frameState) { return false; } - const typedArray = this._typedArray; - const indexDatatype = this._indexDatatype; + let typedArray = this._typedArray; + let indexDatatype = this._indexDatatype; if (defined(this._dracoLoader)) { try { const ready = this._dracoLoader.process(frameState); if (ready) { const dracoLoader = this._dracoLoader; - const typedArray = dracoLoader.decodedData.indices.typedArray; + typedArray = dracoLoader.decodedData.indices.typedArray; this._typedArray = typedArray; // The index datatype may be a smaller datatype after draco decode - this._indexDatatype = ComponentDatatype.fromTypedArray(typedArray); + indexDatatype = ComponentDatatype.fromTypedArray(typedArray); + this._indexDatatype = indexDatatype; } } catch (error) { handleError(this, error); diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index f2aa0cc0ae90..7d31ff1f09df 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -268,7 +268,7 @@ function GltfLoader(options) { this._loaderPromises = []; this._textureLoaders = []; this._texturesPromises = []; - this._textureCallbacks = {}; + this._textureCallbacks = []; this._bufferViewLoaders = []; this._geometryLoaders = []; this._geometryCallbacks = []; @@ -849,6 +849,26 @@ function loadAccessorValues(accessor, typedArray, values, useQuaternion) { return values; } +async function loadAccessorBufferView( + loader, + bufferViewLoader, + gltf, + accessor, + useQuaternion, + values +) { + await bufferViewLoader.load(); + if (loader.isDestroyed()) { + return; + } + + const bufferViewTypedArray = bufferViewLoader.typedArray; + const typedArray = getPackedTypedArray(gltf, accessor, bufferViewTypedArray); + + useQuaternion = defaultValue(useQuaternion, false); + loadAccessorValues(accessor, typedArray, values, useQuaternion); +} + function loadAccessor(loader, gltf, accessorId, useQuaternion) { const accessor = gltf.accessors[accessorId]; const accessorCount = accessor.count; @@ -857,23 +877,14 @@ function loadAccessor(loader, gltf, accessorId, useQuaternion) { const bufferViewId = accessor.bufferView; if (defined(bufferViewId)) { const bufferViewLoader = getBufferViewLoader(loader, gltf, bufferViewId); - const promise = (async () => { - await bufferViewLoader.load(); - if (loader.isDestroyed()) { - return; - } - - const bufferViewTypedArray = bufferViewLoader.typedArray; - const typedArray = getPackedTypedArray( - gltf, - accessor, - bufferViewTypedArray - ); - - useQuaternion = defaultValue(useQuaternion, false); - loadAccessorValues(accessor, typedArray, values, useQuaternion); - })(); - + const promise = loadAccessorBufferView( + loader, + bufferViewLoader, + gltf, + accessor, + useQuaternion, + values + ); loader._loaderPromises.push(promise); return values; diff --git a/packages/engine/Source/Scene/GltfStructuralMetadataLoader.js b/packages/engine/Source/Scene/GltfStructuralMetadataLoader.js index ee10c33b4b78..7bd6129a086a 100644 --- a/packages/engine/Source/Scene/GltfStructuralMetadataLoader.js +++ b/packages/engine/Source/Scene/GltfStructuralMetadataLoader.js @@ -115,6 +115,34 @@ Object.defineProperties(GltfStructuralMetadataLoader.prototype, { }, }); +async function loadResources(loader) { + try { + const bufferViewsPromise = loadBufferViews(loader); + const texturesPromise = loadTextures(loader); + const schemaPromise = loadSchema(loader); + + await Promise.all([bufferViewsPromise, texturesPromise, schemaPromise]); + + if (loader.isDestroyed()) { + return; + } + + loader._gltf = undefined; // No longer need to hold onto the glTF + + loader._state = ResourceLoaderState.LOADED; + return loader; + } catch (error) { + if (loader.isDestroyed()) { + return; + } + + loader.unload(); + loader._state = ResourceLoaderState.FAILED; + const errorMessage = "Failed to load structural metadata"; + throw loader.getError(errorMessage, error); + } +} + /** * Loads the resource. * @returns {Promise} A promise which resolves to the loader when the resource loading is completed. @@ -126,33 +154,7 @@ GltfStructuralMetadataLoader.prototype.load = function () { } this._state = ResourceLoaderState.LOADING; - this._promise = (async () => { - try { - const bufferViewsPromise = loadBufferViews(this); - const texturesPromise = loadTextures(this); - const schemaPromise = loadSchema(this); - - await Promise.all([bufferViewsPromise, texturesPromise, schemaPromise]); - - if (this.isDestroyed()) { - return; - } - - this._gltf = undefined; // No longer need to hold onto the glTF - - this._state = ResourceLoaderState.LOADED; - return this; - } catch (error) { - if (this.isDestroyed()) { - return; - } - - this.unload(); - this._state = ResourceLoaderState.FAILED; - const errorMessage = "Failed to load structural metadata"; - throw this.getError(errorMessage, error); - } - })(); + this._promise = loadResources(this); return this._promise; }; diff --git a/packages/engine/Source/Scene/GltfTextureLoader.js b/packages/engine/Source/Scene/GltfTextureLoader.js index 277c077bb42a..ce02ace44958 100644 --- a/packages/engine/Source/Scene/GltfTextureLoader.js +++ b/packages/engine/Source/Scene/GltfTextureLoader.js @@ -118,6 +118,40 @@ Object.defineProperties(GltfTextureLoader.prototype, { const scratchTextureJob = new CreateTextureJob(); +async function loadResources(loader) { + const resourceCache = loader._resourceCache; + try { + const imageLoader = resourceCache.getImageLoader({ + gltf: loader._gltf, + imageId: loader._imageId, + gltfResource: loader._gltfResource, + baseResource: loader._baseResource, + }); + loader._imageLoader = imageLoader; + await imageLoader.load(); + + if (loader.isDestroyed()) { + return; + } + + // Now wait for process() to run to finish loading + loader._image = imageLoader.image; + loader._mipLevels = imageLoader.mipLevels; + loader._state = ResourceLoaderState.LOADED; + + return loader; + } catch (error) { + if (loader.isDestroyed()) { + return; + } + + loader.unload(); + loader._state = ResourceLoaderState.FAILED; + const errorMessage = "Failed to load texture"; + throw loader.getError(errorMessage, error); + } +} + /** * Loads the resource. * @returns {Promise} A promise which resolves to the loader when the resource loading is completed. @@ -129,40 +163,7 @@ GltfTextureLoader.prototype.load = async function () { } this._state = ResourceLoaderState.LOADING; - const resourceCache = this._resourceCache; - this._promise = (async () => { - try { - const imageLoader = resourceCache.getImageLoader({ - gltf: this._gltf, - imageId: this._imageId, - gltfResource: this._gltfResource, - baseResource: this._baseResource, - }); - this._imageLoader = imageLoader; - await imageLoader.load(); - - if (this.isDestroyed()) { - return; - } - - // Now wait for process() to run to finish loading - this._image = imageLoader.image; - this._mipLevels = imageLoader.mipLevels; - this._state = ResourceLoaderState.LOADED; - - return this; - } catch (error) { - if (this.isDestroyed()) { - return; - } - - this.unload(); - this._state = ResourceLoaderState.FAILED; - const errorMessage = "Failed to load texture"; - throw this.getError(errorMessage, error); - } - })(); - + this._promise = loadResources(this); return this._promise; }; diff --git a/packages/engine/Source/Scene/I3SDataProvider.js b/packages/engine/Source/Scene/I3SDataProvider.js index d284e00bf5cf..5b1d4527348d 100644 --- a/packages/engine/Source/Scene/I3SDataProvider.js +++ b/packages/engine/Source/Scene/I3SDataProvider.js @@ -67,12 +67,12 @@ import Rectangle from "../Core/Rectangle.js"; * * Initialization options for the I3SDataProvider constructor * - * @property {Resource|string} [options.url] The url of the I3S dataset. Deprecated. - * @property {string} [options.name] The name of the I3S dataset. - * @property {boolean} [options.show=true] Determines if the dataset will be shown. - * @property {ArcGISTiledElevationTerrainProvider|Promise} [options.geoidTiledTerrainProvider] Tiled elevation provider describing an Earth Gravitational Model. If defined, geometry will be shifted based on the offsets given by this provider. Required to position I3S data sets with gravity-related height at the correct location. - * @property {boolean} [options.traceFetches=false] Debug option. When true, log a message whenever an I3S tile is fetched. - * @property {Cesium3DTileset.ConstructorOptions} [options.cesium3dTilesetOptions] Object containing options to pass to an internally created {@link Cesium3DTileset}. See {@link Cesium3DTileset} for list of valid properties. All options can be used with the exception of url and show which are overridden by values from I3SDataProvider. + * @property {Resource|string} [url] The url of the I3S dataset. Deprecated. + * @property {string} [name] The name of the I3S dataset. + * @property {boolean} [show=true] Determines if the dataset will be shown. + * @property {ArcGISTiledElevationTerrainProvider|Promise} [geoidTiledTerrainProvider] Tiled elevation provider describing an Earth Gravitational Model. If defined, geometry will be shifted based on the offsets given by this provider. Required to position I3S data sets with gravity-related height at the correct location. + * @property {boolean} [traceFetches=false] Debug option. When true, log a message whenever an I3S tile is fetched. + * @property {Cesium3DTileset.ConstructorOptions} [cesium3dTilesetOptions] Object containing options to pass to an internally created {@link Cesium3DTileset}. See {@link Cesium3DTileset} for list of valid properties. All options can be used with the exception of url and show which are overridden by values from I3SDataProvider. */ /** @@ -710,6 +710,30 @@ function getTiles(terrainProvider, extent) { }); } +async function loadGeoidData(provider) { + // Load tiles from arcgis + const geoidTerrainProvider = provider._geoidTiledTerrainProvider; + + if (!defined(geoidTerrainProvider)) { + console.log( + "No Geoid Terrain service provided - no geoid conversion will be performed." + ); + return; + } + + try { + const heightMaps = await getCoveredTiles( + geoidTerrainProvider, + provider._extent + ); + provider._geoidDataList = heightMaps; + } catch (error) { + console.log( + "Error retrieving Geoid Terrain tiles - no geoid conversion will be performed." + ); + } +} + /** * @private */ @@ -717,29 +741,8 @@ I3SDataProvider.prototype.loadGeoidData = async function () { if (defined(this._geoidDataPromise)) { return this._geoidDataPromise; } - this._geoidDataPromise = (async () => { - // Load tiles from arcgis - const geoidTerrainProvider = this._geoidTiledTerrainProvider; - - if (!defined(geoidTerrainProvider)) { - console.log( - "No Geoid Terrain service provided - no geoid conversion will be performed." - ); - return; - } - try { - const heightMaps = await getCoveredTiles( - geoidTerrainProvider, - this._extent - ); - this._geoidDataList = heightMaps; - } catch (error) { - console.log( - "Error retrieving Geoid Terrain tiles - no geoid conversion will be performed." - ); - } - })(); + this._geoidDataPromise = loadGeoidData(this); return this._geoidDataPromise; }; diff --git a/packages/engine/Source/Scene/Model/ModelUtility.js b/packages/engine/Source/Scene/Model/ModelUtility.js index af90b10e4900..877be6bdb24b 100644 --- a/packages/engine/Source/Scene/Model/ModelUtility.js +++ b/packages/engine/Source/Scene/Model/ModelUtility.js @@ -369,13 +369,9 @@ ModelUtility.supportedExtensions = { * supported. If an unsupported extension is found, this throws * a {@link RuntimeError} with the extension name. * -<<<<<<< HEAD - * @param {Array} extensionsRequired The extensionsRequired array in the glTF. + * @param {string[]} extensionsRequired The extensionsRequired array in the glTF. * * @exception {RuntimeError} Unsupported glTF Extension -======= - * @param {string[]} extensionsRequired The extensionsRequired array in the glTF. ->>>>>>> no-ready-promises */ ModelUtility.checkSupportedExtensions = function (extensionsRequired) { const length = extensionsRequired.length; diff --git a/packages/engine/Source/Scene/Multiple3DTileContent.js b/packages/engine/Source/Scene/Multiple3DTileContent.js index c5ebb7e8a819..65578e434449 100644 --- a/packages/engine/Source/Scene/Multiple3DTileContent.js +++ b/packages/engine/Source/Scene/Multiple3DTileContent.js @@ -485,32 +485,11 @@ async function createInnerContents(multipleContents) { return; } - const createPromise = async (arrayBuffer, i) => { - if (!defined(arrayBuffer)) { - // Content was not fetched. The error was handled in - // the fetch promise. Return undefined to indicate partial failure. - return; - } - - try { - const contents = await createInnerContent( - multipleContents, - arrayBuffer, - i - ); - return contents; - } catch (error) { - handleInnerContentFailed(multipleContents, i, error); - } - }; - - const promises = []; - for (let i = 0; i < arrayBuffers.length; ++i) { - const arrayBuffer = arrayBuffers[i]; - promises.push(createPromise(arrayBuffer, i)); - } + const promises = arrayBuffers.map((arrayBuffer, i) => + createInnerContent(multipleContents, arrayBuffer, i) + ); - // Even if we had a partial success, mark that we finished creating + // Even if we had a partial success (in which case the inner promise will be handled, but the content will nit be returned), mark that we finished creating // contents const contents = await Promise.all(promises); multipleContents._contentsCreated = true; @@ -519,59 +498,69 @@ async function createInnerContents(multipleContents) { } async function createInnerContent(multipleContents, arrayBuffer, index) { - const preprocessed = preprocess3DTileContent(arrayBuffer); - - if (preprocessed.contentType === Cesium3DTileContentType.EXTERNAL_TILESET) { - throw new RuntimeError( - "External tilesets are disallowed inside multiple contents" - ); + if (!defined(arrayBuffer)) { + // Content was not fetched. The error was handled in + // the fetch promise. Return undefined to indicate partial failure. + return; } - multipleContents._disableSkipLevelOfDetail = - multipleContents._disableSkipLevelOfDetail || - preprocessed.contentType === Cesium3DTileContentType.GEOMETRY || - preprocessed.contentType === Cesium3DTileContentType.VECTOR; + try { + const preprocessed = preprocess3DTileContent(arrayBuffer); - const tileset = multipleContents._tileset; - const resource = multipleContents._innerContentResources[index]; - const tile = multipleContents._tile; - - let content; - const contentFactory = Cesium3DTileContentFactory[preprocessed.contentType]; - if (defined(preprocessed.binaryPayload)) { - content = await Promise.resolve( - contentFactory( - tileset, - tile, - resource, - preprocessed.binaryPayload.buffer, - 0 - ) - ); - } else { - // JSON formats - content = await Promise.resolve( - contentFactory(tileset, tile, resource, preprocessed.jsonPayload) - ); - } + if (preprocessed.contentType === Cesium3DTileContentType.EXTERNAL_TILESET) { + throw new RuntimeError( + "External tilesets are disallowed inside multiple contents" + ); + } - const contentHeader = multipleContents._innerContentHeaders[index]; + multipleContents._disableSkipLevelOfDetail = + multipleContents._disableSkipLevelOfDetail || + preprocessed.contentType === Cesium3DTileContentType.GEOMETRY || + preprocessed.contentType === Cesium3DTileContentType.VECTOR; + + const tileset = multipleContents._tileset; + const resource = multipleContents._innerContentResources[index]; + const tile = multipleContents._tile; + + let content; + const contentFactory = Cesium3DTileContentFactory[preprocessed.contentType]; + if (defined(preprocessed.binaryPayload)) { + content = await Promise.resolve( + contentFactory( + tileset, + tile, + resource, + preprocessed.binaryPayload.buffer, + 0 + ) + ); + } else { + // JSON formats + content = await Promise.resolve( + contentFactory(tileset, tile, resource, preprocessed.jsonPayload) + ); + } - if (tile.hasImplicitContentMetadata) { - const subtree = tile.implicitSubtree; - const coordinates = tile.implicitCoordinates; - content.metadata = subtree.getContentMetadataView(coordinates, index); - } else if (!tile.hasImplicitContent) { - content.metadata = findContentMetadata(tileset, contentHeader); - } + const contentHeader = multipleContents._innerContentHeaders[index]; - const groupMetadata = findGroupMetadata(tileset, contentHeader); - if (defined(groupMetadata)) { - content.group = new Cesium3DContentGroup({ - metadata: groupMetadata, - }); + if (tile.hasImplicitContentMetadata) { + const subtree = tile.implicitSubtree; + const coordinates = tile.implicitCoordinates; + content.metadata = subtree.getContentMetadataView(coordinates, index); + } else if (!tile.hasImplicitContent) { + content.metadata = findContentMetadata(tileset, contentHeader); + } + + const groupMetadata = findGroupMetadata(tileset, contentHeader); + if (defined(groupMetadata)) { + content.group = new Cesium3DContentGroup({ + metadata: groupMetadata, + }); + } + return content; + } catch (error) { + handleInnerContentFailed(multipleContents, index, error); } - return content; } function handleInnerContentFailed(multipleContents, index, error) { diff --git a/packages/engine/Specs/Scene/Empty3DTileContentSpec.js b/packages/engine/Specs/Scene/Empty3DTileContentSpec.js index e20fa68da1d9..9cb79921e44a 100644 --- a/packages/engine/Specs/Scene/Empty3DTileContentSpec.js +++ b/packages/engine/Specs/Scene/Empty3DTileContentSpec.js @@ -19,7 +19,6 @@ describe("Scene/Empty3DTileContent", function () { expect(content.texturesByteLength).toBe(0); expect(content.batchTableByteLength).toBe(0); expect(content.innerContents).toBeUndefined(); - expect(content.readyPromise).toBeUndefined(); expect(content.tileset).toBe(mockTileset); expect(content.tile).toBe(mockTile); expect(content.url).toBeUndefined(); diff --git a/packages/engine/Specs/Scene/GltfLoaderSpec.js b/packages/engine/Specs/Scene/GltfLoaderSpec.js index 8b8689061846..53c2d9c2dbdf 100644 --- a/packages/engine/Specs/Scene/GltfLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfLoaderSpec.js @@ -176,34 +176,6 @@ describe( ); }); - // This cannot actually ever happen due to gltfPipeline's updateVersion - xit("load throws if the gltf specifies an unknown version", async function () { - spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( - Promise.resolve( - generateJsonBuffer({ - asset: { - version: "3.0", - }, - }).buffer - ) - ); - - const gltfResource = new Resource({ - url: "https://example.com/model.glb", - }); - - const gltfLoader = new GltfLoader({ - gltfResource: gltfResource, - releaseGltfJson: true, - }); - gltfLoaders.push(gltfLoader); - - await expectAsync(gltfLoader.load()).toBeRejectedWithError( - RuntimeError, - "Failed to load glTF\nUnsupported glTF version: 0.1" - ); - }); - it("load throws if an unsupported extension is required", async function () { function modifyGltf(gltf) { gltf.extensionsRequired = ["NOT_supported_extension"]; diff --git a/packages/engine/Specs/Scene/GroundPolylinePrimitiveSpec.js b/packages/engine/Specs/Scene/GroundPolylinePrimitiveSpec.js index 6ea779e687cf..ef37af8f3853 100644 --- a/packages/engine/Specs/Scene/GroundPolylinePrimitiveSpec.js +++ b/packages/engine/Specs/Scene/GroundPolylinePrimitiveSpec.js @@ -202,7 +202,7 @@ describe( expect(groundPolylinePrimitive.debugShowShadowVolume).toEqual(true); }); - it("releases geometry instances when releaseGeometryInstances is true", function () { + it("releases geometry instances when releaseGeometryInstances is true", async function () { if (!GroundPolylinePrimitive.isSupported(scene)) { return; } @@ -215,13 +215,16 @@ describe( expect(groundPolylinePrimitive.geometryInstances).toBeDefined(); scene.groundPrimitives.add(groundPolylinePrimitive); - scene.renderForSpecs(); - return groundPolylinePrimitive.readyPromise.then(function () { - expect(groundPolylinePrimitive.geometryInstances).not.toBeDefined(); + + await pollToPromise(() => { + scene.renderForSpecs(); + return groundPolylinePrimitive.ready; }); + + expect(groundPolylinePrimitive.geometryInstances).not.toBeDefined(); }); - it("does not release geometry instances when releaseGeometryInstances is false", function () { + it("does not release geometry instances when releaseGeometryInstances is false", async function () { if (!GroundPolylinePrimitive.isSupported(scene)) { return; } @@ -234,13 +237,15 @@ describe( expect(groundPolylinePrimitive.geometryInstances).toBeDefined(); scene.groundPrimitives.add(groundPolylinePrimitive); - scene.renderForSpecs(); - return groundPolylinePrimitive.readyPromise.then(function () { - expect(groundPolylinePrimitive.geometryInstances).toBeDefined(); + await pollToPromise(() => { + scene.renderForSpecs(); + return groundPolylinePrimitive.ready; }); + + expect(groundPolylinePrimitive.geometryInstances).toBeDefined(); }); - it("adds afterRender promise to frame state", function () { + it("becomes ready", async function () { if (!GroundPolylinePrimitive.isSupported(scene)) { return; } @@ -252,11 +257,12 @@ describe( }); scene.groundPrimitives.add(groundPolylinePrimitive); - scene.renderForSpecs(); - - return groundPolylinePrimitive.readyPromise.then(function (param) { - expect(param.ready).toBe(true); + await pollToPromise(() => { + scene.renderForSpecs(); + return groundPolylinePrimitive.ready; }); + + expect(groundPolylinePrimitive.ready).toBe(true); }); it("does not render when geometryInstances is undefined", function () { @@ -293,7 +299,7 @@ describe( expect(scene.frameState.commandList.length).toEqual(0); }); - it("becomes ready when show is false", function () { + it("becomes ready when show is false", async function () { if (!GroundPolylinePrimitive.isSupported(scene)) { return; } @@ -305,17 +311,12 @@ describe( ); groundPolylinePrimitive.show = false; - let ready = false; - groundPolylinePrimitive.readyPromise.then(function () { - ready = true; - }); - - return pollToPromise(function () { + await pollToPromise(() => { scene.renderForSpecs(); - return ready; - }).then(function () { - expect(ready).toEqual(true); + return groundPolylinePrimitive.ready; }); + + expect(groundPolylinePrimitive.ready).toBeTrue(); }); it("does not render other than for the color or pick pass", function () { diff --git a/packages/engine/Specs/Scene/GroundPrimitiveSpec.js b/packages/engine/Specs/Scene/GroundPrimitiveSpec.js index 18d1b30ad38a..20a453354b24 100644 --- a/packages/engine/Specs/Scene/GroundPrimitiveSpec.js +++ b/packages/engine/Specs/Scene/GroundPrimitiveSpec.js @@ -229,7 +229,7 @@ describe( expect(primitive.debugShowShadowVolume).toEqual(true); }); - it("releases geometry instances when releaseGeometryInstances is true", function () { + it("releases geometry instances when releaseGeometryInstances is true", async function () { if (!GroundPrimitive.isSupported(scene)) { return; } @@ -242,13 +242,15 @@ describe( expect(primitive.geometryInstances).toBeDefined(); scene.groundPrimitives.add(primitive); - scene.renderForSpecs(); - return primitive.readyPromise.then(function () { - expect(primitive.geometryInstances).not.toBeDefined(); + await pollToPromise(() => { + scene.renderForSpecs(); + return primitive.ready; }); + + expect(primitive.geometryInstances).not.toBeDefined(); }); - it("does not release geometry instances when releaseGeometryInstances is false", function () { + it("does not release geometry instances when releaseGeometryInstances is false", async function () { if (!GroundPrimitive.isSupported(scene)) { return; } @@ -261,13 +263,15 @@ describe( expect(primitive.geometryInstances).toBeDefined(); scene.groundPrimitives.add(primitive); - scene.renderForSpecs(); - return primitive.readyPromise.then(function () { - expect(primitive.geometryInstances).toBeDefined(); + await pollToPromise(() => { + scene.renderForSpecs(); + return primitive.ready; }); + + expect(primitive.geometryInstances).toBeDefined(); }); - it("adds afterRender promise to frame state", function () { + it("becomes ready", async function () { if (!GroundPrimitive.isSupported(scene)) { return; } @@ -279,11 +283,12 @@ describe( }); scene.groundPrimitives.add(primitive); - scene.renderForSpecs(); - - return primitive.readyPromise.then(function (param) { - expect(param.ready).toBe(true); + await pollToPromise(() => { + scene.renderForSpecs(); + return primitive.ready; }); + + expect(primitive.ready).toBe(true); }); it("does not render when geometryInstances is undefined", function () { @@ -321,7 +326,7 @@ describe( expect(scene.frameState.commandList.length).toEqual(0); }); - it("becomes ready when show is false", function () { + it("becomes ready when show is false", async function () { if (!GroundPrimitive.isSupported(scene)) { return; } @@ -334,17 +339,12 @@ describe( }) ); - let ready = false; - primitive.readyPromise.then(function () { - ready = true; - }); - - return pollToPromise(function () { + await pollToPromise(() => { scene.renderForSpecs(); - return ready; - }).then(function () { - expect(ready).toEqual(true); + return primitive.ready; }); + + expect(primitive.ready).toBe(true); }); it("does not render other than for the color or pick pass", function () { @@ -1191,7 +1191,7 @@ describe( expect(scene).notToPick(); }); - it("internally invalid asynchronous geometry resolves promise and sets ready", function () { + it("internally invalid asynchronous geometry becomes ready", async function () { if (!GroundPrimitive.isSupported(scene)) { return; } @@ -1210,18 +1210,15 @@ describe( scene.groundPrimitives.add(primitive); - return pollToPromise(function () { + await pollToPromise(() => { scene.renderForSpecs(); return primitive.ready; - }).then(function () { - return primitive.readyPromise.then(function (arg) { - expect(arg).toBe(primitive); - expect(primitive.ready).toBe(true); - }); }); + + expect(primitive.ready).toBe(true); }); - it("internally invalid synchronous geometry resolves promise and sets ready", function () { + it("internally invalid synchronous geometry becomes ready", async function () { if (!GroundPrimitive.isSupported(scene)) { return; } @@ -1241,15 +1238,12 @@ describe( scene.groundPrimitives.add(primitive); - return pollToPromise(function () { + await pollToPromise(() => { scene.renderForSpecs(); return primitive.ready; - }).then(function () { - return primitive.readyPromise.then(function (arg) { - expect(arg).toBe(primitive); - expect(primitive.ready).toBe(true); - }); }); + + expect(primitive.ready).toBe(true); }); it("update throws when batched instance colors are different and materials on GroundPrimitives are not supported", function () { diff --git a/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js b/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js index 1c645260b02d..2c80710f8a4c 100644 --- a/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js +++ b/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js @@ -2,50 +2,7 @@ import { Model } from "../../../index.js"; import pollToPromise from "../../../../../Specs/pollToPromise.js"; async function loadAndZoomToModelAsync(options, scene) { - const model = await Model.fromGltfAsync({ - url: options.url, - gltf: options.gltf, - basePath: options.basePath, - show: options.show, - modelMatrix: options.modelMatrix, - scale: options.scale, - minimumPixelSize: options.minimumPixelSize, - maximumScale: options.maximumScale, - id: options.id, - allowPicking: options.allowPicking, - incrementallyLoadTextures: options.incrementallyLoadTextures, - asynchronous: options.asynchronous, - clampAnimations: options.clampAnimations, - shadows: options.shadows, - debugShowBoundingVolume: options.debugShowBoundingVolume, - enableDebugWireframe: options.enableDebugWireframe, - debugWireframe: options.debugWireframe, - cull: options.cull, - opaquePass: options.opaquePass, - upAxis: options.upAxis, - forwardAxis: options.forwardAxis, - customShader: options.customShader, - content: options.content, - heightReference: options.heightReference, - scene: options.scene, - distanceDisplayCondition: options.distanceDisplayCondition, - color: options.color, - colorBlendAmount: options.colorBlendAmount, - colorBlendMode: options.colorBlendMode, - silhouetteColor: options.silhouetteColor, - silhouetteSize: options.silhouetteSize, - clippingPlanes: options.clippingPlanes, - lightColor: options.lightColor, - imageBasedLighting: options.imageBasedLighting, - backFaceCulling: options.backFaceCulling, - credit: options.credit, - showCreditsOnScreen: options.showCreditsOnScreen, - projectTo2D: options.projectTo2D, - featureIdLabel: options.featureIdLabel, - instanceFeatureIdLabel: options.instanceFeatureIdLabel, - classificationType: options.classificationType, - }); - + const model = await Model.fromGltfAsync(options); scene.primitives.add(model); await pollToPromise( diff --git a/packages/widgets/Specs/Viewer/ViewerSpec.js b/packages/widgets/Specs/Viewer/ViewerSpec.js index 443d94001a58..e2cdec48edd3 100644 --- a/packages/widgets/Specs/Viewer/ViewerSpec.js +++ b/packages/widgets/Specs/Viewer/ViewerSpec.js @@ -1304,7 +1304,7 @@ describe( }); }); - function loadTimeDynamicPointCloud(viewer) { + async function loadTimeDynamicPointCloud(viewer) { const scene = viewer.scene; const clock = viewer.clock; @@ -1338,12 +1338,12 @@ describe( scene.primitives.add(pointCloud); - return pollToPromise(function () { + await pollToPromise(function () { scene.render(); return defined(pointCloud.boundingSphere); - }).then(function () { - return pointCloud.readyPromise; }); + + return pointCloud; } it("zoomTo zooms to TimeDynamicPointCloud with default offset when offset not defined", function () { From 76defbae4bf832bd13a70b21f5412ea2e0d2e6db Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Tue, 28 Mar 2023 14:39:20 -0400 Subject: [PATCH 17/18] Deprecation & documentation cleanup --- .../Scene/Cesium3DTilesVoxelProvider.js | 19 +++++- .../Source/Scene/Composite3DTileContent.js | 40 ++++++++++++- .../engine/Source/Scene/Empty3DTileContent.js | 30 ++++++++++ .../Source/Scene/Geometry3DTileContent.js | 41 ++++++++++++- .../Source/Scene/Implicit3DTileContent.js | 33 +++++++++++ .../Source/Scene/Model/Model3DTileContent.js | 59 +++++++++++++++++++ .../Source/Scene/Multiple3DTileContent.js | 40 ++++++++++++- .../Source/Scene/Tileset3DTileContent.js | 32 ++++++++++ .../Source/Scene/Vector3DTileContent.js | 39 ++++++++++++ .../Source/Scene/Vector3DTileGeometry.js | 3 + .../engine/Source/Scene/Vector3DTilePoints.js | 3 + .../Source/Scene/Vector3DTilePolygons.js | 3 + .../Source/Scene/Vector3DTilePolylines.js | 3 + 13 files changed, 340 insertions(+), 5 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index 5a84f8f9d99c..c319710e159a 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -176,7 +176,14 @@ function Cesium3DTilesVoxelProvider(options) { } Object.defineProperties(Cesium3DTilesVoxelProvider.prototype, { - /** @inheritdoc */ + /** + * Gets the promise that will be resolved when the provider is ready for use. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {Promise} + * @readonly + * @deprecated + */ readyPromise: { get: function () { deprecationWarning( @@ -186,7 +193,15 @@ Object.defineProperties(Cesium3DTilesVoxelProvider.prototype, { return this._readyPromise; }, }, - /** @inheritdoc */ + + /** + * Gets a value indicating whether or not the provider is ready for use. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {boolean} + * @readonly + * @deprecated + */ ready: { get: function () { deprecationWarning( diff --git a/packages/engine/Source/Scene/Composite3DTileContent.js b/packages/engine/Source/Scene/Composite3DTileContent.js index e61d10e819ac..824b01186956 100644 --- a/packages/engine/Source/Scene/Composite3DTileContent.js +++ b/packages/engine/Source/Scene/Composite3DTileContent.js @@ -1,5 +1,6 @@ import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import getMagic from "../Core/getMagic.js"; import RuntimeError from "../Core/RuntimeError.js"; @@ -30,6 +31,11 @@ function Composite3DTileContent(tileset, tile, resource, contents) { this._metadata = undefined; this._group = undefined; this._ready = false; + + this._resolveContent = undefined; + this._readyPromise = new Promise((resolve) => { + this._resolveContent = resolve; + }); } Object.defineProperties(Composite3DTileContent.prototype, { @@ -126,12 +132,41 @@ Object.defineProperties(Composite3DTileContent.prototype, { }, }, + /** + * Returns true when the tile's content is ready to render; otherwise false + * + * @memberof Composite3DTileContent.prototype + * + * @type {boolean} + * @readonly + * @private + */ ready: { get: function () { return this._ready; }, }, + /** + * Gets the promise that will be resolved when the tile's content is ready to render. + * + * @memberof Composite3DTileContent.prototype + * + * @type {Promise} + * @readonly + * @deprecated + * @private + */ + readyPromise: { + get: function () { + deprecationWarning( + "Composite3DTileContent.readyPromise", + "Composite3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Composite3DTileContent.ready to return true instead." + ); + return this._readyPromise; + }, + }, + tileset: { get: function () { return this._tileset; @@ -333,7 +368,10 @@ Composite3DTileContent.prototype.update = function (tileset, frameState) { ready = ready && contents[i].ready; } - this._ready = ready; + if (!this._ready && ready) { + this._ready = true; + this._resolveContent(this); + } }; Composite3DTileContent.prototype.isDestroyed = function () { diff --git a/packages/engine/Source/Scene/Empty3DTileContent.js b/packages/engine/Source/Scene/Empty3DTileContent.js index 13f427b0e02b..b9aa4f82144d 100644 --- a/packages/engine/Source/Scene/Empty3DTileContent.js +++ b/packages/engine/Source/Scene/Empty3DTileContent.js @@ -1,3 +1,4 @@ +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; @@ -64,12 +65,41 @@ Object.defineProperties(Empty3DTileContent.prototype, { }, }, + /** + * Returns true when the tile's content is ready to render; otherwise false + * + * @memberof Empty3DTileContent.prototype + * + * @type {boolean} + * @readonly + * @private + */ ready: { get: function () { return true; }, }, + /** + * Gets the promise that will be resolved when the tile's content is ready to render. + * + * @memberof Empty3DTileContent.prototype + * + * @type {Promise} + * @readonly + * @deprecated + * @private + */ + readyPromise: { + get: function () { + deprecationWarning( + "Empty3DTileContent.readyPromise", + "Empty3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Empty3DTileContent.ready to return true instead." + ); + return Promise.resolve(this); + }, + }, + tileset: { get: function () { return this._tileset; diff --git a/packages/engine/Source/Scene/Geometry3DTileContent.js b/packages/engine/Source/Scene/Geometry3DTileContent.js index f9ea775d9108..4486ffbb8bf2 100644 --- a/packages/engine/Source/Scene/Geometry3DTileContent.js +++ b/packages/engine/Source/Scene/Geometry3DTileContent.js @@ -1,6 +1,7 @@ import Cartesian3 from "../Core/Cartesian3.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js"; @@ -43,6 +44,13 @@ function Geometry3DTileContent( this._group = undefined; this._ready = false; + + // This is for backwards compatibility. It can be removed once readyPromise is removed. + this._resolveContent = undefined; + this._readyPromise = new Promise((resolve) => { + this._resolveContent = resolve; + }); + initialize(this, arrayBuffer, byteOffset); } @@ -97,12 +105,41 @@ Object.defineProperties(Geometry3DTileContent.prototype, { }, }, + /** + * Returns true when the tile's content is ready to render; otherwise false + * + * @memberof Geometry3DTileContent.prototype + * + * @type {boolean} + * @readonly + * @private + */ ready: { get: function () { return this._ready; }, }, + /** + * Gets the promise that will be resolved when the tile's content is ready to render. + * + * @memberof Geometry3DTileContent.prototype + * + * @type {Promise} + * @readonly + * @deprecated + * @private + */ + readyPromise: { + get: function () { + deprecationWarning( + "Geometry3DTileContent.readyPromise", + "Geometry3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Geometry3DTileContent.ready to return true instead." + ); + return this._readyPromise; + }, + }, + tileset: { get: function () { return this._tileset; @@ -291,7 +328,8 @@ function initialize(content, arrayBuffer, byteOffset) { byteOffset += sizeOfUint32; if (byteLength === 0) { - this._ready = true; + content._ready = true; + content._resolveContent(content); return; } @@ -512,6 +550,7 @@ Geometry3DTileContent.prototype.update = function (tileset, frameState) { if (defined(this._batchTable) && this._geometries.ready) { this._batchTable.update(tileset, frameState); this._ready = true; + this._resolveContent(this); } }; diff --git a/packages/engine/Source/Scene/Implicit3DTileContent.js b/packages/engine/Source/Scene/Implicit3DTileContent.js index 8f93e9dc5de3..7f6a8314afb7 100644 --- a/packages/engine/Source/Scene/Implicit3DTileContent.js +++ b/packages/engine/Source/Scene/Implicit3DTileContent.js @@ -4,6 +4,7 @@ import clone from "../Core/clone.js"; import combine from "../Core/combine.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import CesiumMath from "../Core/Math.js"; @@ -73,6 +74,7 @@ function Implicit3DTileContent(tileset, tile, resource) { this._url = subtreeResource.getUrlComponent(true); this._ready = false; + this._readyPromise = undefined; } Object.defineProperties(Implicit3DTileContent.prototype, { @@ -118,12 +120,41 @@ Object.defineProperties(Implicit3DTileContent.prototype, { }, }, + /** + * Returns true when the tile's content is ready to render; otherwise false + * + * @memberof Implicit3DTileContent.prototype + * + * @type {boolean} + * @readonly + * @private + */ ready: { get: function () { return this._ready; }, }, + /** + * Gets the promise that will be resolved when the tile's content is ready to render. + * + * @memberof Implicit3DTileContent.prototype + * + * @type {Promise} + * @readonly + * @deprecated + * @private + */ + readyPromise: { + get: function () { + deprecationWarning( + "Implicit3DTileContent.readyPromise", + "Implicit3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Implicit3DTileContent.ready to return true instead." + ); + return this._readyPromise; + }, + }, + tileset: { get: function () { return this._tileset; @@ -229,6 +260,8 @@ Implicit3DTileContent.fromSubtreeJson = async function ( content._implicitSubtree = subtree; expandSubtree(content, subtree); content._ready = true; + content._readyPromise = Promise.resolve(content); + return content; }; diff --git a/packages/engine/Source/Scene/Model/Model3DTileContent.js b/packages/engine/Source/Scene/Model/Model3DTileContent.js index 52c51879f4b8..cd222a39c47d 100644 --- a/packages/engine/Source/Scene/Model/Model3DTileContent.js +++ b/packages/engine/Source/Scene/Model/Model3DTileContent.js @@ -2,6 +2,7 @@ import Color from "../../Core/Color.js"; import combine from "../../Core/combine.js"; import defined from "../../Core/defined.js"; import destroyObject from "../../Core/destroyObject.js"; +import deprecationWarning from "../../Core/deprecationWarning.js"; import DeveloperError from "../../Core/DeveloperError.js"; import Pass from "../../Renderer/Pass.js"; import ModelAnimationLoop from "../ModelAnimationLoop.js"; @@ -29,6 +30,9 @@ function Model3DTileContent(tileset, tile, resource) { this._metadata = undefined; this._group = undefined; this._ready = false; + + this._resolveContent = undefined; + this._readyPromise = undefined; } Object.defineProperties(Model3DTileContent.prototype, { @@ -85,12 +89,41 @@ Object.defineProperties(Model3DTileContent.prototype, { }, }, + /** + * Returns true when the tile's content is ready to render; otherwise false + * + * @memberof Model3DTileContent.prototype + * + * @type {boolean} + * @readonly + * @private + */ ready: { get: function () { return this._ready; }, }, + /** + * Gets the promise that will be resolved when the tile's content is ready to render. + * + * @memberof Model3DTileContent.prototype + * + * @type {Promise} + * @readonly + * @deprecated + * @private + */ + readyPromise: { + get: function () { + deprecationWarning( + "Model3DTileContent.readyPromise", + "Model3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Model3DTileContent.ready to return true instead." + ); + return this._readyPromise; + }, + }, + tileset: { get: function () { return this._tileset; @@ -254,6 +287,7 @@ Model3DTileContent.prototype.update = function (tileset, frameState) { }); this._ready = true; + this._resolveContent = this._resolveContent && this._resolveContent(this); } }; @@ -290,6 +324,11 @@ Model3DTileContent.fromGltf = async function (tileset, tile, resource, gltf) { const model = await Model.fromGltfAsync(modelOptions); content._model = model; + // This is for backwards compatibility. It can be removed once readyPromise is removed. + content._readyPromise = new Promise((resolve) => { + content._resolveContent = resolve; + }); + return content; }; @@ -324,6 +363,11 @@ Model3DTileContent.fromB3dm = async function ( const model = await Model.fromB3dm(modelOptions); content._model = model; + // This is for backwards compatibility. It can be removed once readyPromise is removed. + content._readyPromise = new Promise((resolve) => { + content._resolveContent = resolve; + }); + return content; }; @@ -352,6 +396,11 @@ Model3DTileContent.fromI3dm = async function ( const model = await Model.fromI3dm(modelOptions); content._model = model; + // This is for backwards compatibility. It can be removed once readyPromise is removed. + content._readyPromise = new Promise((resolve) => { + content._resolveContent = resolve; + }); + return content; }; @@ -379,6 +428,11 @@ Model3DTileContent.fromPnts = async function ( const model = await Model.fromPnts(modelOptions); content._model = model; + // This is for backwards compatibility. It can be removed once readyPromise is removed. + content._readyPromise = new Promise((resolve) => { + content._resolveContent = resolve; + }); + return content; }; @@ -404,6 +458,11 @@ Model3DTileContent.fromGeoJson = async function ( const model = await Model.fromGeoJson(modelOptions); content._model = model; + // This is for backwards compatibility. It can be removed once readyPromise is removed. + content._readyPromise = new Promise((resolve) => { + content._resolveContent = resolve; + }); + return content; }; diff --git a/packages/engine/Source/Scene/Multiple3DTileContent.js b/packages/engine/Source/Scene/Multiple3DTileContent.js index 65578e434449..10de75a5c2ff 100644 --- a/packages/engine/Source/Scene/Multiple3DTileContent.js +++ b/packages/engine/Source/Scene/Multiple3DTileContent.js @@ -1,4 +1,5 @@ import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import Request from "../Core/Request.js"; @@ -56,6 +57,11 @@ function Multiple3DTileContent(tileset, tile, tilesetResource, contentsJson) { this._requests = new Array(contentCount); this._ready = false; + this._resolveContent = undefined; + this._readyPromise = new Promise((resolve) => { + this._resolveContent = resolve; + }); + this._innerContentResources = new Array(contentCount); this._serverKeys = new Array(contentCount); @@ -211,6 +217,15 @@ Object.defineProperties(Multiple3DTileContent.prototype, { }, }, + /** + * Returns true when the tile's content is ready to render; otherwise false + * + * @memberof Multiple3DTileContent.prototype + * + * @type {boolean} + * @readonly + * @private + */ ready: { get: function () { if (!this._contentsCreated) { @@ -221,6 +236,26 @@ Object.defineProperties(Multiple3DTileContent.prototype, { }, }, + /** + * Gets the promise that will be resolved when the tile's content is ready to render. + * + * @memberof Multiple3DTileContent.prototype + * + * @type {Promise} + * @readonly + * @deprecated + * @private + */ + readyPromise: { + get: function () { + deprecationWarning( + "Multiple3DTileContent.readyPromise", + "Multiple3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Multiple3DTileContent.ready to return true instead." + ); + return this._readyPromise; + }, + }, + tileset: { get: function () { return this._tileset; @@ -636,7 +671,10 @@ Multiple3DTileContent.prototype.update = function (tileset, frameState) { ready = ready && contents[i].ready; } - this._ready = ready; + if (!this._ready && ready) { + this._ready = true; + this._resolveContent(this); + } }; Multiple3DTileContent.prototype.isDestroyed = function () { diff --git a/packages/engine/Source/Scene/Tileset3DTileContent.js b/packages/engine/Source/Scene/Tileset3DTileContent.js index 9d5ba1172c9a..34559190b62c 100644 --- a/packages/engine/Source/Scene/Tileset3DTileContent.js +++ b/packages/engine/Source/Scene/Tileset3DTileContent.js @@ -1,4 +1,5 @@ import destroyObject from "../Core/destroyObject.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; /** * Represents content for a tile in a @@ -24,6 +25,7 @@ function Tileset3DTileContent(tileset, tile, resource) { this._group = undefined; this._ready = false; + this._readyPromise = Promise.resolve(this); } Object.defineProperties(Tileset3DTileContent.prototype, { @@ -69,12 +71,41 @@ Object.defineProperties(Tileset3DTileContent.prototype, { }, }, + /** + * Returns true when the tile's content is ready to render; otherwise false + * + * @memberof Tileset3DTileContent.prototype + * + * @type {boolean} + * @readonly + * @private + */ ready: { get: function () { return this._ready; }, }, + /** + * Gets the promise that will be resolved when the tile's content is ready to render. + * + * @memberof Tileset3DTileContent.prototype + * + * @type {Promise} + * @readonly + * @deprecated + * @private + */ + readyPromise: { + get: function () { + deprecationWarning( + "Tileset3DTileContent.readyPromise", + "Tileset3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Tileset3DTileContent.ready to return true instead." + ); + return this._readyPromise; + }, + }, + tileset: { get: function () { return this._tileset; @@ -130,6 +161,7 @@ Tileset3DTileContent.fromJson = function (tileset, tile, resource, json) { const content = new Tileset3DTileContent(tileset, tile, resource); content._tileset.loadTileset(content._resource, json, content._tile); content._ready = true; + return content; }; diff --git a/packages/engine/Source/Scene/Vector3DTileContent.js b/packages/engine/Source/Scene/Vector3DTileContent.js index 396e1a9e52fb..565e8839aaa3 100644 --- a/packages/engine/Source/Scene/Vector3DTileContent.js +++ b/packages/engine/Source/Scene/Vector3DTileContent.js @@ -1,6 +1,7 @@ import Cartesian3 from "../Core/Cartesian3.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import Ellipsoid from "../Core/Ellipsoid.js"; @@ -52,6 +53,13 @@ function Vector3DTileContent(tileset, tile, resource, arrayBuffer, byteOffset) { this._group = undefined; this._ready = false; + + // This is here for backwards compatibility and can be removed when readyPromise is removed. + this._resolveContent = undefined; + this._readyPromise = new Promise((resolve) => { + this._resolveContent = resolve; + }); + initialize(this, arrayBuffer, byteOffset); } @@ -120,12 +128,41 @@ Object.defineProperties(Vector3DTileContent.prototype, { }, }, + /** + * Returns true when the tile's content is ready to render; otherwise false + * + * @memberof Vector3DTileContent.prototype + * + * @type {boolean} + * @readonly + * @private + */ ready: { get: function () { return this._ready; }, }, + /** + * Gets the promise that will be resolved when the tile's content is ready to render. + * + * @memberof Vector3DTileContent.prototype + * + * @type {Promise} + * @readonly + * @deprecated + * @private + */ + readyPromise: { + get: function () { + deprecationWarning( + "Vector3DTileContent.readyPromise", + "Vector3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Vector3DTileContent.ready to return true instead." + ); + return this._readyPromise; + }, + }, + tileset: { get: function () { return this._tileset; @@ -298,6 +335,7 @@ function initialize(content, arrayBuffer, byteOffset) { if (byteLength === 0) { content._ready = true; + content._resolveContent(content); return; } @@ -714,6 +752,7 @@ Vector3DTileContent.prototype.update = function (tileset, frameState) { } this._batchTable.update(tileset, frameState); this._ready = true; + this._resolveContent(this); } }; diff --git a/packages/engine/Source/Scene/Vector3DTileGeometry.js b/packages/engine/Source/Scene/Vector3DTileGeometry.js index 39ce487b1fc1..c6e4c7688079 100644 --- a/packages/engine/Source/Scene/Vector3DTileGeometry.js +++ b/packages/engine/Source/Scene/Vector3DTileGeometry.js @@ -108,6 +108,7 @@ Object.defineProperties(Vector3DTileGeometry.prototype, { * * @type {number} * @readonly + * @private */ trianglesLength: { get: function () { @@ -125,6 +126,7 @@ Object.defineProperties(Vector3DTileGeometry.prototype, { * * @type {number} * @readonly + * @private */ geometryByteLength: { get: function () { @@ -140,6 +142,7 @@ Object.defineProperties(Vector3DTileGeometry.prototype, { * @memberof Vector3DTileGeometry.prototype * @type {boolean} * @readonly + * @private */ ready: { get: function () { diff --git a/packages/engine/Source/Scene/Vector3DTilePoints.js b/packages/engine/Source/Scene/Vector3DTilePoints.js index 42c65633d7f9..f66fc7901a01 100644 --- a/packages/engine/Source/Scene/Vector3DTilePoints.js +++ b/packages/engine/Source/Scene/Vector3DTilePoints.js @@ -67,6 +67,7 @@ Object.defineProperties(Vector3DTilePoints.prototype, { * * @type {boolean} * @readonly + * @private */ ready: { get: function () { @@ -81,6 +82,7 @@ Object.defineProperties(Vector3DTilePoints.prototype, { * * @type {number} * @readonly + * @private */ pointsLength: { get: function () { @@ -95,6 +97,7 @@ Object.defineProperties(Vector3DTilePoints.prototype, { * * @type {number} * @readonly + * @private */ texturesByteLength: { get: function () { diff --git a/packages/engine/Source/Scene/Vector3DTilePolygons.js b/packages/engine/Source/Scene/Vector3DTilePolygons.js index dfc26848fe2b..06a6d3de907e 100644 --- a/packages/engine/Source/Scene/Vector3DTilePolygons.js +++ b/packages/engine/Source/Scene/Vector3DTilePolygons.js @@ -108,6 +108,7 @@ Object.defineProperties(Vector3DTilePolygons.prototype, { * * @type {number} * @readonly + * @private */ trianglesLength: { get: function () { @@ -125,6 +126,7 @@ Object.defineProperties(Vector3DTilePolygons.prototype, { * * @type {number} * @readonly + * @private */ geometryByteLength: { get: function () { @@ -140,6 +142,7 @@ Object.defineProperties(Vector3DTilePolygons.prototype, { * @memberof Vector3DTilePolygons.prototype * @type {boolean} * @readonly + * @private */ ready: { get: function () { diff --git a/packages/engine/Source/Scene/Vector3DTilePolylines.js b/packages/engine/Source/Scene/Vector3DTilePolylines.js index 70baacb6d69c..781f568b5118 100644 --- a/packages/engine/Source/Scene/Vector3DTilePolylines.js +++ b/packages/engine/Source/Scene/Vector3DTilePolylines.js @@ -99,6 +99,7 @@ Object.defineProperties(Vector3DTilePolylines.prototype, { * * @type {number} * @readonly + * @private */ trianglesLength: { get: function () { @@ -113,6 +114,7 @@ Object.defineProperties(Vector3DTilePolylines.prototype, { * * @type {number} * @readonly + * @private */ geometryByteLength: { get: function () { @@ -125,6 +127,7 @@ Object.defineProperties(Vector3DTilePolylines.prototype, { * @memberof Vector3DTilePolylines.prototype * @type {boolean} * @readonly + * @private */ ready: { get: function () { From d3af1ba2bd2aca4bbb626411550d97eb690dc6f4 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 28 Mar 2023 16:27:52 -0400 Subject: [PATCH 18/18] Fix typo in comment --- packages/engine/Source/Scene/Multiple3DTileContent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Multiple3DTileContent.js b/packages/engine/Source/Scene/Multiple3DTileContent.js index 10de75a5c2ff..10b8998d3e7c 100644 --- a/packages/engine/Source/Scene/Multiple3DTileContent.js +++ b/packages/engine/Source/Scene/Multiple3DTileContent.js @@ -524,7 +524,7 @@ async function createInnerContents(multipleContents) { createInnerContent(multipleContents, arrayBuffer, i) ); - // Even if we had a partial success (in which case the inner promise will be handled, but the content will nit be returned), mark that we finished creating + // Even if we had a partial success (in which case the inner promise will be handled, but the content will not be returned), mark that we finished creating // contents const contents = await Promise.all(promises); multipleContents._contentsCreated = true;