import Check from "../../Core/Check.js"; import defined from "../../Core/defined.js"; import destroyObject from "../../Core/destroyObject.js"; import DeveloperError from "../../Core/DeveloperError.js"; import ImageryConfiguration from "./ImageryConfiguration.js"; import ModelPrimitiveImagery from "./ModelPrimitiveImagery.js"; /** * A class managing the draping of imagery on a Model. * * An instance of this class is created in the Model constructor. It will * create the data structures that carry the information that is required * for mapping imagery textures on model primitives. * * It offers two functions for managing the lifecycle of this draping process: * * The update function is called from the Model.update * function in each frame. It will create one ModelPrimitiveImagery * instance for each primitive that appears in the model, and call the * update function of these instances, respectively. * * The ready getter will be used to determine whether the * draping computations are done, and the update process of the Model * can continue, eventually causing the model.ready flag to * become true. The model imagery counts as "ready" when all * the imagery layers of the model are ready, and all the * ModelPrimitiveImagery instances are ready. * * @private */ class ModelImagery { /** * Creates a new instance * * @param {Model} model The model * @throws {DeveloperError} If the model is not defined */ constructor(model) { //>>includeStart('debug', pragmas.debug); Check.defined("model", model); //>>includeEnd('debug'); /** * The model that this instance was created for. * * @type {Model} * @readonly * @private */ this._model = model; /** * One for each primitive * that appears in the model. * * Initially, this is undefined. When the update * function is called and all imagery layers that are associated with the * model are ready, this is initialized with one instance * of a ModelPrimitiveImagery per runtime primitive (i.e. one for * each model.sceneGraph._runtimeNodes[n]._runtimePrimitives[p]) * * @type {ModelPrimitiveImagery[]|undefined} * @private */ this._modelPrimitiveImageries = undefined; /** * One ImageryConfiguration object for each ImageryLayer * that is associated with the model. * * This is used for determining whether the configuration (relevant property * values) of an imagery layer has been changed since the previous * update call, which should cause the draw commands of the * model to be reset. * * @type {ImageryConfiguration[]} * @private */ this._imageryConfigurations = []; } /** * The update function that is called from Model.update in * each frame. * * This checks whether the imagery layer objects that are associated * with the model are all ready. If they are not yet * ready, then nothing is done. * * Otherwise, this just calls the update function of * the _modelPrimitiveImageries (creating them if they had * not been created yet). * * @param {FrameState} frameState The frame state */ update(frameState) { //>>includeStart('debug', pragmas.debug); Check.defined("frameState", frameState); //>>includeEnd('debug'); if (!this._hasImagery) { return; } if (!this._allImageryLayersReady) { return; } if (!defined(this._modelPrimitiveImageries)) { this._modelPrimitiveImageries = this._createModelPrimitiveImageries(); } this._updateModelPrimitiveImageries(frameState); this._checkForModifiedImageryConfigurations(); } /** * Creates the ModelPrimitiveImagery array that contains * one ModelPrimitiveImagery for each primitive that is * contained in the model. * * @returns {ModelPrimitiveImagery[]} The model primitive imageries * @private */ _createModelPrimitiveImageries() { const model = this._model; const runtimeNodesAndPrimitives = this._collectRuntimeNodesAndPrimitives(); const modelPrimitiveImageries = []; const length = runtimeNodesAndPrimitives.length; for (let i = 0; i < length; i++) { const runtimeNodeAndPrimitive = runtimeNodesAndPrimitives[i]; const runtimeNode = runtimeNodeAndPrimitive.runtimeNode; const runtimePrimitive = runtimeNodeAndPrimitive.runtimePrimitive; const modelPrimitiveImagery = new ModelPrimitiveImagery( model, runtimeNode, runtimePrimitive, ); runtimePrimitive.primitive.modelPrimitiveImagery = modelPrimitiveImagery; modelPrimitiveImageries.push(modelPrimitiveImagery); } return modelPrimitiveImageries; } /** * Computes all runtime nodes and primitives of the model. * * This is just the array that contains a * { runtimeNode, runtimePrimitive } * for each * model.sceneGraph._runtimeNodes[n]._runtimePrimitives[p]. * * @returns {object[]} The runtime nodes and primitives * @private */ _collectRuntimeNodesAndPrimitives() { const model = this._model; const sceneGraph = model.sceneGraph; const runtimeNodes = sceneGraph._runtimeNodes; const runtimeNodesAndPrimitives = []; for (let i = 0; i < runtimeNodes.length; i++) { const runtimeNode = runtimeNodes[i]; if (!defined(runtimeNode)) { continue; } for (let j = 0; j < runtimeNode.runtimePrimitives.length; j++) { const runtimePrimitive = runtimeNode.runtimePrimitives[j]; runtimeNodesAndPrimitives.push({ runtimeNode: runtimeNode, runtimePrimitive: runtimePrimitive, }); } } return runtimeNodesAndPrimitives; } /** * Just calls update on each ModelPrimitiveImagery * as part of the update of this class. * * @private */ _updateModelPrimitiveImageries(frameState) { //>>includeStart('debug', pragmas.debug); Check.defined("frameState", frameState); //>>includeEnd('debug'); if (!defined(this._modelPrimitiveImageries)) { throw new DeveloperError( "The modelPrimitiveImageries have not been created", ); } const modelPrimitiveImageries = this._modelPrimitiveImageries; const length = modelPrimitiveImageries.length; for (let i = 0; i < length; i++) { const modelPrimitiveImagery = modelPrimitiveImageries[i]; modelPrimitiveImagery.update(frameState); } } /** * Destroy and delete all ModelPrimitiveImagery instances * if they already have been created. */ _deleteModelPrimitiveImageries() { const modelPrimitiveImageries = this._modelPrimitiveImageries; if (!defined(modelPrimitiveImageries)) { return; } const length = modelPrimitiveImageries.length; for (let i = 0; i < length; i++) { const modelPrimitiveImagery = modelPrimitiveImageries[i]; modelPrimitiveImagery.destroy(); } delete this._modelPrimitiveImageries; } /** * Returns whether this instance is "ready". * * This means that all imagery layers that are associated with the model * are ready, and all ModelPrimitiveImagery * instances are ready. * * When this is true, then the mapping computations are * complete and the structures containing the mapping information have * been initialized. Otherwise, subsequent calls to update * will perform the necessary computation until this getter eventually * returns true. * * @returns {boolean} Whether this instance is "ready" */ get ready() { if (!this._hasImagery) { return true; } if (!this._allImageryLayersReady) { return false; } if (!this._allModelPrimitiveImageriesReady) { return false; } return true; } /** * Returns whether the model has imagery layers associated with it. * * @private */ get _hasImagery() { const model = this._model; const imageryLayers = model.imageryLayers; return defined(imageryLayers) && imageryLayers.length > 0; } /** * Returns whether all imagery layers that are associated with the * model are ready. * * If the model does not have imagery, then this always returns * true. Otherwise, it returns whether each imagery * layer is ready. * * @private */ get _allImageryLayersReady() { if (!this._hasImagery) { return true; } const imageryLayers = this._model.imageryLayers; const length = imageryLayers.length; for (let i = 0; i < length; i++) { const imageryLayer = imageryLayers.get(i); if (!imageryLayer.ready) { return false; } } return true; } /** * Returns whether all ModelPrimitiveImagery instances * are are ready. * * @private */ get _allModelPrimitiveImageriesReady() { const modelPrimitiveImageries = this._modelPrimitiveImageries; if (!defined(modelPrimitiveImageries)) { return false; } const length = modelPrimitiveImageries.length; for (let i = 0; i < length; i++) { const modelPrimitiveImagery = modelPrimitiveImageries[i]; if (!modelPrimitiveImagery.ready) { return false; } } return true; } /** * Check whether any of the settings of any imagery layer (like alpha * or hue) has been changed since the last call to the update * function. * * If this is the case, the draw commands of the model will be reset. */ _checkForModifiedImageryConfigurations() { if (this._imageryConfigurationsModified()) { this._updateImageryConfigurations(); const model = this._model; model.resetDrawCommands(); } } /** * Returns whether any setting of an imagery layer (like alpha or hue) has * been changed since the last time the ImageryConfiguration * objects have been updated. * * @returns {boolean} Whether there was a modification */ _imageryConfigurationsModified() { const model = this._model; const imageryLayers = model.imageryLayers; const imageryConfigurations = this._imageryConfigurations; if (imageryLayers.length !== imageryConfigurations.length) { return true; } for (let i = 0; i < imageryLayers.length; i++) { const imageryLayer = imageryLayers.get(i); const imageryConfiguration = imageryConfigurations[i]; if (imageryLayer.show !== imageryConfiguration.show) { return true; } if (imageryLayer.alpha !== imageryConfiguration.alpha) { return true; } if (imageryLayer.brightness !== imageryConfiguration.brightness) { return true; } if (imageryLayer.contrast !== imageryConfiguration.contrast) { return true; } if (imageryLayer.hue !== imageryConfiguration.hue) { return true; } if (imageryLayer.saturation !== imageryConfiguration.saturation) { return true; } if (imageryLayer.gamma !== imageryConfiguration.gamma) { return true; } if (imageryLayer.colorToAlpha !== imageryConfiguration.colorToAlpha) { return true; } } return false; } /** * Create one ImageryConfiguration object for each imagery * layer that appears in the model, and store them as the * _imageryConfigurations. */ _updateImageryConfigurations() { const model = this._model; const imageryLayers = model.imageryLayers; const imageryConfigurations = this._imageryConfigurations; imageryConfigurations.length = imageryLayers.length; for (let i = 0; i < imageryLayers.length; i++) { const imageryLayer = imageryLayers.get(i); imageryConfigurations[i] = new ImageryConfiguration(imageryLayer); } } /** * Returns whether this object was destroyed. * * If this object was destroyed, calling any function other than * isDestroyed will result in a {@link DeveloperError}. * * @returns {boolean} Whether this object was destroyed */ isDestroyed() { return false; } /** * Destroys this object and all its resources. */ destroy() { if (this.isDestroyed()) { return; } this._deleteModelPrimitiveImageries(); return destroyObject(this); } } export default ModelImagery;