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 when from "../ThirdParty/when.js";
import parseFeatureMetadata from "./parseFeatureMetadata.js";
import parseFeatureMetadataLegacy from "./parseFeatureMetadataLegacy.js";
import ResourceCache from "./ResourceCache.js";
import ResourceLoader from "./ResourceLoader.js";
import ResourceLoaderState from "./ResourceLoaderState.js";
/**
* Loads glTF feature metadata
*
* Implements the {@link ResourceLoader} interface.
*
*
* @alias GltfFeatureMetadataLoader
* @constructor
* @augments ResourceLoader
*
* @param {Object} options Object with the following properties:
* @param {Object} options.gltf The glTF JSON.
* @param {String} [options.extension] The EXT_mesh_features 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 GltfFeatureMetadataLoader(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var gltf = options.gltf;
var extension = options.extension;
var extensionLegacy = options.extensionLegacy;
var gltfResource = options.gltfResource;
var baseResource = options.baseResource;
var supportedImageFormats = options.supportedImageFormats;
var cacheKey = options.cacheKey;
var 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._featureMetadata = undefined;
this._state = ResourceLoaderState.UNLOADED;
this._promise = when.defer();
}
if (defined(Object.create)) {
GltfFeatureMetadataLoader.prototype = Object.create(ResourceLoader.prototype);
GltfFeatureMetadataLoader.prototype.constructor = GltfFeatureMetadataLoader;
}
Object.defineProperties(GltfFeatureMetadataLoader.prototype, {
/**
* A promise that resolves to the resource when the resource is ready.
*
* @memberof GltfFeatureMetadataLoader.prototype
*
* @type {Promise.}
* @readonly
* @private
*/
promise: {
get: function () {
return this._promise.promise;
},
},
/**
* The cache key of the resource.
*
* @memberof GltfFeatureMetadataLoader.prototype
*
* @type {String}
* @readonly
* @private
*/
cacheKey: {
get: function () {
return this._cacheKey;
},
},
/**
* Feature metadata.
*
* @memberof GltfFeatureMetadataLoader.prototype
*
* @type {FeatureMetadata}
* @readonly
* @private
*/
featureMetadata: {
get: function () {
return this._featureMetadata;
},
},
});
/**
* Loads the resource.
* @private
*/
GltfFeatureMetadataLoader.prototype.load = function () {
var bufferViewsPromise = loadBufferViews(this);
var texturesPromise = loadTextures(this);
var schemaPromise = loadSchema(this);
this._gltf = undefined; // No longer need to hold onto the glTF
this._state = ResourceLoaderState.LOADING;
var that = this;
when
.all([bufferViewsPromise, texturesPromise, schemaPromise])
.then(function (results) {
if (that.isDestroyed()) {
return;
}
var bufferViews = results[0];
var textures = results[1];
var schema = results[2];
if (defined(that._extension)) {
that._featureMetadata = parseFeatureMetadata({
extension: that._extension,
schema: schema,
bufferViews: bufferViews,
textures: textures,
});
} else {
that._featureMetadata = parseFeatureMetadataLegacy({
extension: that._extensionLegacy,
schema: schema,
bufferViews: bufferViews,
textures: textures,
});
}
that._state = ResourceLoaderState.READY;
that._promise.resolve(that);
})
.otherwise(function (error) {
if (that.isDestroyed()) {
return;
}
that.unload();
that._state = ResourceLoaderState.FAILED;
var errorMessage = "Failed to load feature metadata";
that._promise.reject(that.getError(errorMessage, error));
});
};
function gatherBufferViewIdsFromProperties(properties, bufferViewIdSet) {
for (var propertyId in properties) {
if (properties.hasOwnProperty(propertyId)) {
var property = properties[propertyId];
var bufferView = property.bufferView;
var arrayOffsetBufferView = property.arrayOffsetBufferView;
var 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) {
var propertyTables = extension.propertyTables;
var bufferViewIdSet = {};
if (defined(propertyTables)) {
for (var i = 0; i < propertyTables.length; i++) {
var propertyTable = propertyTables[i];
gatherBufferViewIdsFromProperties(
propertyTable.properties,
bufferViewIdSet
);
}
}
return bufferViewIdSet;
}
function gatherUsedBufferViewIdsLegacy(extensionLegacy) {
var featureTables = extensionLegacy.featureTables;
var bufferViewIdSet = {};
if (defined(featureTables)) {
for (var featureTableId in featureTables) {
if (featureTables.hasOwnProperty(featureTableId)) {
var featureTable = featureTables[featureTableId];
var properties = featureTable.properties;
if (defined(properties)) {
gatherBufferViewIdsFromProperties(properties, bufferViewIdSet);
}
}
}
}
return bufferViewIdSet;
}
function loadBufferViews(featureMetadataLoader) {
var bufferViewIds;
if (defined(featureMetadataLoader._extension)) {
bufferViewIds = gatherUsedBufferViewIds(featureMetadataLoader._extension);
} else {
bufferViewIds = gatherUsedBufferViewIdsLegacy(
featureMetadataLoader._extensionLegacy
);
}
// Load the buffer views
var bufferViewPromises = [];
var bufferViewLoaders = {};
for (var bufferViewId in bufferViewIds) {
if (bufferViewIds.hasOwnProperty(bufferViewId)) {
var bufferViewLoader = ResourceCache.loadBufferView({
gltf: featureMetadataLoader._gltf,
bufferViewId: parseInt(bufferViewId),
gltfResource: featureMetadataLoader._gltfResource,
baseResource: featureMetadataLoader._baseResource,
});
bufferViewPromises.push(bufferViewLoader.promise);
featureMetadataLoader._bufferViewLoaders.push(bufferViewLoader);
bufferViewLoaders[bufferViewId] = bufferViewLoader;
}
}
// Return a promise to a map of buffer view IDs to typed arrays
return when.all(bufferViewPromises).then(function () {
var bufferViews = {};
for (var bufferViewId in bufferViewLoaders) {
if (bufferViewLoaders.hasOwnProperty(bufferViewId)) {
var bufferViewLoader = bufferViewLoaders[bufferViewId];
// Copy the typed array and let the underlying ArrayBuffer be freed
var bufferViewTypedArray = new Uint8Array(bufferViewLoader.typedArray);
bufferViews[bufferViewId] = bufferViewTypedArray;
}
}
// Buffer views can be unloaded after the data has been copied
unloadBufferViews(featureMetadataLoader);
return bufferViews;
});
}
function gatherUsedTextureIds(extension) {
// Gather the used textures
var textureIds = {};
var propertyTextures = extension.propertyTextures;
if (defined(propertyTextures)) {
for (var i = 0; i < propertyTextures.length; i++) {
var propertyTexture = propertyTextures[i];
if (defined(propertyTexture.properties)) {
// The property texture JSON is also a glTF textureInfo
textureIds[propertyTexture.index] = propertyTexture;
}
}
}
return textureIds;
}
function gatherTextureIdsFromProperties(properties, textureIds) {
for (var propertyId in properties) {
if (properties.hasOwnProperty(propertyId)) {
var property = properties[propertyId];
var textureInfo = property.texture;
textureIds[textureInfo.index] = textureInfo;
}
}
}
function gatherUsedTextureIdsLegacy(extensionLegacy) {
// Gather the used textures
var textureIds = {};
var featureTextures = extensionLegacy.featureTextures;
if (defined(featureTextures)) {
for (var featureTextureId in featureTextures) {
if (featureTextures.hasOwnProperty(featureTextureId)) {
var featureTexture = featureTextures[featureTextureId];
var properties = featureTexture.properties;
if (defined(properties)) {
gatherTextureIdsFromProperties(properties, textureIds);
}
}
}
}
return textureIds;
}
function loadTextures(featureMetadataLoader) {
var textureIds;
if (defined(featureMetadataLoader._extension)) {
textureIds = gatherUsedTextureIds(featureMetadataLoader._extension);
} else {
textureIds = gatherUsedTextureIdsLegacy(
featureMetadataLoader._extensionLegacy
);
}
var gltf = featureMetadataLoader._gltf;
var gltfResource = featureMetadataLoader._gltfResource;
var baseResource = featureMetadataLoader._baseResource;
var supportedImageFormats = featureMetadataLoader._supportedImageFormats;
var asynchronous = featureMetadataLoader._asynchronous;
// Load the textures
var texturePromises = [];
var textureLoaders = {};
for (var textureId in textureIds) {
if (textureIds.hasOwnProperty(textureId)) {
var textureLoader = ResourceCache.loadTexture({
gltf: gltf,
textureInfo: textureIds[textureId],
gltfResource: gltfResource,
baseResource: baseResource,
supportedImageFormats: supportedImageFormats,
asynchronous: asynchronous,
});
texturePromises.push(textureLoader.promise);
featureMetadataLoader._textureLoaders.push(textureLoader);
textureLoaders[textureId] = textureLoader;
}
}
// Return a promise to a map of texture IDs to Texture objects
return when.all(texturePromises).then(function () {
var textures = {};
for (var textureId in textureLoaders) {
if (textureLoaders.hasOwnProperty(textureId)) {
var textureLoader = textureLoaders[textureId];
textures[textureId] = textureLoader.texture;
}
}
return textures;
});
}
function loadSchema(featureMetadataLoader) {
var extension = defaultValue(
featureMetadataLoader._extension,
featureMetadataLoader._extensionLegacy
);
var schemaLoader;
if (defined(extension.schemaUri)) {
var resource = featureMetadataLoader._baseResource.getDerivedResource({
url: extension.schemaUri,
});
schemaLoader = ResourceCache.loadSchema({
resource: resource,
});
} else {
schemaLoader = ResourceCache.loadSchema({
schema: extension.schema,
});
}
featureMetadataLoader._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
*/
GltfFeatureMetadataLoader.prototype.process = function (frameState) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("frameState", frameState);
//>>includeEnd('debug');
if (this._state !== ResourceLoaderState.LOADING) {
return;
}
var textureLoaders = this._textureLoaders;
var textureLoadersLength = textureLoaders.length;
for (var i = 0; i < textureLoadersLength; ++i) {
var textureLoader = textureLoaders[i];
textureLoader.process(frameState);
}
};
function unloadBufferViews(featureMetadataLoader) {
var bufferViewLoaders = featureMetadataLoader._bufferViewLoaders;
var bufferViewLoadersLength = bufferViewLoaders.length;
for (var i = 0; i < bufferViewLoadersLength; ++i) {
ResourceCache.unload(bufferViewLoaders[i]);
}
featureMetadataLoader._bufferViewLoaders.length = 0;
}
function unloadTextures(featureMetadataLoader) {
var textureLoaders = featureMetadataLoader._textureLoaders;
var textureLoadersLength = textureLoaders.length;
for (var i = 0; i < textureLoadersLength; ++i) {
ResourceCache.unload(textureLoaders[i]);
}
featureMetadataLoader._textureLoaders.length = 0;
}
/**
* Unloads the resource.
* @private
*/
GltfFeatureMetadataLoader.prototype.unload = function () {
unloadBufferViews(this);
unloadTextures(this);
if (defined(this._schemaLoader)) {
ResourceCache.unload(this._schemaLoader);
}
this._schemaLoader = undefined;
this._featureMetadata = undefined;
};