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;