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 parseStructuralMetadata from "./parseStructuralMetadata.js"; import parseFeatureMetadataLegacy from "./parseFeatureMetadataLegacy.js"; import ResourceCache from "./ResourceCache.js"; import ResourceLoader from "./ResourceLoader.js"; import ResourceLoaderState from "./ResourceLoaderState.js"; /** * Loads glTF structural metadata *

* Implements the {@link ResourceLoader} interface. *

* * @alias GltfStructuralMetadataLoader * @constructor * @augments ResourceLoader * * @param {object} options Object with the following properties: * @param {object} options.gltf The glTF JSON. * @param {string} [options.extension] The EXT_structural_metadata extension object. If this is undefined, then extensionLegacy must be defined. * @param {string} [options.extensionLegacy] The legacy EXT_feature_metadata extension for backwards compatibility. * @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 {SupportedImageFormats} options.supportedImageFormats The supported image formats. * @param {FrameState} options.frameState The frame state. * @param {string} [options.cacheKey] The cache key of the resource. * @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. * * @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 GltfStructuralMetadataLoader(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const extension = options.extension; const extensionLegacy = options.extensionLegacy; const gltfResource = options.gltfResource; const baseResource = options.baseResource; const supportedImageFormats = options.supportedImageFormats; const frameState = options.frameState; const cacheKey = options.cacheKey; const asynchronous = defaultValue(options.asynchronous, true); //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.gltf", gltf); Check.typeOf.object("options.gltfResource", gltfResource); Check.typeOf.object("options.baseResource", baseResource); Check.typeOf.object("options.supportedImageFormats", supportedImageFormats); Check.typeOf.object("options.frameState", frameState); if (!defined(options.extension) && !defined(options.extensionLegacy)) { throw new DeveloperError( "One of options.extension or options.extensionLegacy must be specified" ); } //>>includeEnd('debug'); this._gltfResource = gltfResource; this._baseResource = baseResource; this._gltf = gltf; this._extension = extension; this._extensionLegacy = extensionLegacy; this._supportedImageFormats = supportedImageFormats; this._frameState = frameState; this._cacheKey = cacheKey; this._asynchronous = asynchronous; this._bufferViewLoaders = []; this._bufferViewIds = []; this._textureLoaders = []; this._textureIds = []; this._schemaLoader = undefined; this._structuralMetadata = undefined; this._state = ResourceLoaderState.UNLOADED; this._promise = undefined; } if (defined(Object.create)) { GltfStructuralMetadataLoader.prototype = Object.create( ResourceLoader.prototype ); GltfStructuralMetadataLoader.prototype.constructor = GltfStructuralMetadataLoader; } Object.defineProperties(GltfStructuralMetadataLoader.prototype, { /** * The cache key of the resource. * * @memberof GltfStructuralMetadataLoader.prototype * * @type {string} * @readonly * @private */ cacheKey: { get: function () { return this._cacheKey; }, }, /** * The parsed structural metadata * * @memberof GltfStructuralMetadataLoader.prototype * * @type {StructuralMetadata} * @readonly * @private */ structuralMetadata: { get: function () { return this._structuralMetadata; }, }, }); 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. * @private */ GltfStructuralMetadataLoader.prototype.load = function () { if (defined(this._promise)) { return this._promise; } this._state = ResourceLoaderState.LOADING; this._promise = loadResources(this); return this._promise; }; function gatherBufferViewIdsFromProperties(properties, bufferViewIdSet) { for (const propertyId in properties) { if (properties.hasOwnProperty(propertyId)) { const property = properties[propertyId]; const values = property.values; const arrayOffsets = property.arrayOffsets; const stringOffsets = property.stringOffsets; // Using an object like a mathematical set if (defined(values)) { bufferViewIdSet[values] = true; } if (defined(arrayOffsets)) { bufferViewIdSet[arrayOffsets] = true; } if (defined(stringOffsets)) { bufferViewIdSet[stringOffsets] = true; } } } } function gatherBufferViewIdsFromPropertiesLegacy(properties, bufferViewIdSet) { for (const propertyId in properties) { if (properties.hasOwnProperty(propertyId)) { const property = properties[propertyId]; const bufferView = property.bufferView; const arrayOffsetBufferView = property.arrayOffsetBufferView; const stringOffsetBufferView = property.stringOffsetBufferView; // Using an object like a mathematical set if (defined(bufferView)) { bufferViewIdSet[bufferView] = true; } if (defined(arrayOffsetBufferView)) { bufferViewIdSet[arrayOffsetBufferView] = true; } if (defined(stringOffsetBufferView)) { bufferViewIdSet[stringOffsetBufferView] = true; } } } } function gatherUsedBufferViewIds(extension) { const propertyTables = extension.propertyTables; const bufferViewIdSet = {}; if (defined(propertyTables)) { for (let i = 0; i < propertyTables.length; i++) { const propertyTable = propertyTables[i]; gatherBufferViewIdsFromProperties( propertyTable.properties, bufferViewIdSet ); } } return bufferViewIdSet; } function gatherUsedBufferViewIdsLegacy(extensionLegacy) { const featureTables = extensionLegacy.featureTables; const bufferViewIdSet = {}; if (defined(featureTables)) { for (const featureTableId in featureTables) { if (featureTables.hasOwnProperty(featureTableId)) { const featureTable = featureTables[featureTableId]; const properties = featureTable.properties; if (defined(properties)) { gatherBufferViewIdsFromPropertiesLegacy(properties, bufferViewIdSet); } } } } return bufferViewIdSet; } async function loadBufferViews(structuralMetadataLoader) { let bufferViewIds; if (defined(structuralMetadataLoader._extension)) { bufferViewIds = gatherUsedBufferViewIds( structuralMetadataLoader._extension ); } else { bufferViewIds = gatherUsedBufferViewIdsLegacy( structuralMetadataLoader._extensionLegacy ); } // Load the buffer views const bufferViewPromises = []; for (const bufferViewId in bufferViewIds) { if (bufferViewIds.hasOwnProperty(bufferViewId)) { const bufferViewLoader = ResourceCache.getBufferViewLoader({ gltf: structuralMetadataLoader._gltf, bufferViewId: parseInt(bufferViewId), gltfResource: structuralMetadataLoader._gltfResource, baseResource: structuralMetadataLoader._baseResource, }); structuralMetadataLoader._bufferViewLoaders.push(bufferViewLoader); structuralMetadataLoader._bufferViewIds.push(bufferViewId); bufferViewPromises.push(bufferViewLoader.load()); } } return Promise.all(bufferViewPromises); } function gatherUsedTextureIds(structuralMetadataExtension) { // Gather the used textures const textureIds = {}; const propertyTextures = structuralMetadataExtension.propertyTextures; if (defined(propertyTextures)) { for (let i = 0; i < propertyTextures.length; i++) { const propertyTexture = propertyTextures[i]; const properties = propertyTexture.properties; if (defined(properties)) { gatherTextureIdsFromProperties(properties, textureIds); } } } return textureIds; } function gatherTextureIdsFromProperties(properties, textureIds) { for (const propertyId in properties) { if (properties.hasOwnProperty(propertyId)) { // in EXT_structural_metadata the property is a valid textureInfo. const textureInfo = properties[propertyId]; textureIds[textureInfo.index] = textureInfo; } } } function gatherUsedTextureIdsLegacy(extensionLegacy) { // Gather the used textures const textureIds = {}; const featureTextures = extensionLegacy.featureTextures; if (defined(featureTextures)) { for (const featureTextureId in featureTextures) { if (featureTextures.hasOwnProperty(featureTextureId)) { const featureTexture = featureTextures[featureTextureId]; const properties = featureTexture.properties; if (defined(properties)) { gatherTextureIdsFromPropertiesLegacy(properties, textureIds); } } } } return textureIds; } function gatherTextureIdsFromPropertiesLegacy(properties, textureIds) { for (const propertyId in properties) { if (properties.hasOwnProperty(propertyId)) { const property = properties[propertyId]; const textureInfo = property.texture; textureIds[textureInfo.index] = textureInfo; } } } function loadTextures(structuralMetadataLoader) { let textureIds; if (defined(structuralMetadataLoader._extension)) { textureIds = gatherUsedTextureIds(structuralMetadataLoader._extension); } else { textureIds = gatherUsedTextureIdsLegacy( structuralMetadataLoader._extensionLegacy ); } const gltf = structuralMetadataLoader._gltf; const gltfResource = structuralMetadataLoader._gltfResource; const baseResource = structuralMetadataLoader._baseResource; const supportedImageFormats = structuralMetadataLoader._supportedImageFormats; const frameState = structuralMetadataLoader._frameState; const asynchronous = structuralMetadataLoader._asynchronous; // Load the textures const texturePromises = []; for (const textureId in textureIds) { if (textureIds.hasOwnProperty(textureId)) { const textureLoader = ResourceCache.getTextureLoader({ gltf: gltf, textureInfo: textureIds[textureId], gltfResource: gltfResource, baseResource: baseResource, supportedImageFormats: supportedImageFormats, frameState: frameState, asynchronous: asynchronous, }); structuralMetadataLoader._textureLoaders.push(textureLoader); structuralMetadataLoader._textureIds.push(textureId); texturePromises.push(textureLoader.load()); } } return Promise.all(texturePromises); } async function loadSchema(structuralMetadataLoader) { const extension = defaultValue( structuralMetadataLoader._extension, structuralMetadataLoader._extensionLegacy ); let schemaLoader; if (defined(extension.schemaUri)) { const resource = structuralMetadataLoader._baseResource.getDerivedResource({ url: extension.schemaUri, }); schemaLoader = ResourceCache.getSchemaLoader({ resource: resource, }); } else { schemaLoader = ResourceCache.getSchemaLoader({ schema: extension.schema, }); } structuralMetadataLoader._schemaLoader = schemaLoader; await schemaLoader.load(); if (!schemaLoader.isDestroyed()) { return schemaLoader.schema; } } /** * Processes the resource until it becomes ready. * * @param {FrameState} frameState The frame state. * @private */ GltfStructuralMetadataLoader.prototype.process = function (frameState) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("frameState", frameState); //>>includeEnd('debug'); 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]; 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) { const bufferViewLoaders = structuralMetadataLoader._bufferViewLoaders; const bufferViewLoadersLength = bufferViewLoaders.length; for (let i = 0; i < bufferViewLoadersLength; ++i) { ResourceCache.unload(bufferViewLoaders[i]); } structuralMetadataLoader._bufferViewLoaders.length = 0; structuralMetadataLoader._bufferViewIds.length = 0; } function unloadTextures(structuralMetadataLoader) { const textureLoaders = structuralMetadataLoader._textureLoaders; const textureLoadersLength = textureLoaders.length; for (let i = 0; i < textureLoadersLength; ++i) { ResourceCache.unload(textureLoaders[i]); } structuralMetadataLoader._textureLoaders.length = 0; structuralMetadataLoader._textureIds.length = 0; } /** * Unloads the resource. * @private */ GltfStructuralMetadataLoader.prototype.unload = function () { unloadBufferViews(this); unloadTextures(this); if (defined(this._schemaLoader)) { ResourceCache.unload(this._schemaLoader); } this._schemaLoader = undefined; this._structuralMetadata = undefined; }; export default GltfStructuralMetadataLoader;