import Check from "../Core/Check.js"; import defaultValue from "../Core/defaultValue.js"; import defer from "../Core/defer.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 {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. */ export default 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 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); 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._cacheKey = cacheKey; this._asynchronous = asynchronous; this._bufferViewLoaders = []; this._textureLoaders = []; this._schemaLoader = undefined; this._structuralMetadata = undefined; this._state = ResourceLoaderState.UNLOADED; this._promise = defer(); } if (defined(Object.create)) { GltfStructuralMetadataLoader.prototype = Object.create( ResourceLoader.prototype ); GltfStructuralMetadataLoader.prototype.constructor = GltfStructuralMetadataLoader; } Object.defineProperties(GltfStructuralMetadataLoader.prototype, { /** * A promise that resolves to the resource when the resource is ready. * * @memberof GltfStructuralMetadataLoader.prototype * * @type {Promise.} * @readonly * @private */ promise: { get: function () { return this._promise.promise; }, }, /** * 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; }, }, }); /** * Loads the resource. * @private */ GltfStructuralMetadataLoader.prototype.load = function () { const bufferViewsPromise = loadBufferViews(this); const texturesPromise = loadTextures(this); const schemaPromise = loadSchema(this); this._gltf = undefined; // No longer need to hold onto the glTF this._state = ResourceLoaderState.LOADING; const that = this; Promise.all([bufferViewsPromise, texturesPromise, schemaPromise]) .then(function (results) { if (that.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; that._promise.resolve(that); }) .catch(function (error) { if (that.isDestroyed()) { return; } that.unload(); that._state = ResourceLoaderState.FAILED; const errorMessage = "Failed to load structural metadata"; that._promise.reject(that.getError(errorMessage, error)); }); }; 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; } function loadBufferViews(structuralMetadataLoader) { let bufferViewIds; if (defined(structuralMetadataLoader._extension)) { bufferViewIds = gatherUsedBufferViewIds( structuralMetadataLoader._extension ); } else { bufferViewIds = gatherUsedBufferViewIdsLegacy( structuralMetadataLoader._extensionLegacy ); } // Load the buffer views const bufferViewPromises = []; const bufferViewLoaders = {}; for (const bufferViewId in bufferViewIds) { if (bufferViewIds.hasOwnProperty(bufferViewId)) { const bufferViewLoader = ResourceCache.loadBufferView({ gltf: structuralMetadataLoader._gltf, bufferViewId: parseInt(bufferViewId), gltfResource: structuralMetadataLoader._gltfResource, baseResource: structuralMetadataLoader._baseResource, }); bufferViewPromises.push(bufferViewLoader.promise); structuralMetadataLoader._bufferViewLoaders.push(bufferViewLoader); bufferViewLoaders[bufferViewId] = bufferViewLoader; } } // 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; } } // Buffer views can be unloaded after the data has been copied unloadBufferViews(structuralMetadataLoader); return bufferViews; }); } 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 asynchronous = structuralMetadataLoader._asynchronous; // Load the textures const texturePromises = []; const textureLoaders = {}; for (const textureId in textureIds) { if (textureIds.hasOwnProperty(textureId)) { const textureLoader = ResourceCache.loadTexture({ gltf: gltf, textureInfo: textureIds[textureId], gltfResource: gltfResource, baseResource: baseResource, supportedImageFormats: supportedImageFormats, asynchronous: asynchronous, }); texturePromises.push(textureLoader.promise); structuralMetadataLoader._textureLoaders.push(textureLoader); textureLoaders[textureId] = textureLoader; } } // 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; }); } 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.loadSchema({ resource: resource, }); } else { schemaLoader = ResourceCache.loadSchema({ schema: extension.schema, }); } structuralMetadataLoader._schemaLoader = schemaLoader; return schemaLoader.promise.then(function (schemaLoader) { 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.LOADING) { return; } const textureLoaders = this._textureLoaders; const textureLoadersLength = textureLoaders.length; for (let i = 0; i < textureLoadersLength; ++i) { const textureLoader = textureLoaders[i]; textureLoader.process(frameState); } }; 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; } 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; } /** * 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; };