UNPKG

@cesium/engine

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

973 lines (898 loc) 33.3 kB
import defined from "../../Core/defined.js"; import DeveloperError from "../../Core/DeveloperError.js"; import Matrix4 from "../../Core/Matrix4.js"; import Check from "../../Core/Check.js"; import destroyObject from "../../Core/destroyObject.js"; import ImageryState from "../ImageryState.js"; import ImageryCoverage from "./ImageryCoverage.js"; import ModelImageryMapping from "./ModelImageryMapping.js"; import ModelUtility from "./ModelUtility.js"; import MappedPositions from "./MappedPositions.js"; import Buffer from "../../Renderer/Buffer.js"; import BufferUsage from "../../Renderer/BufferUsage.js"; /** * A class managing the draping of imagery on a single model primitive. * * The <code>ModelImagery</code> class creates one instance of this * class for each primitive that appears in the model. * * It is responsible for computing * - the mapped (cartographic) positions of the primitive * - the imagery tiles that are covered by these mapped positions * - the texture coordinates (attributes) that correspond to these mapped positions * * @private */ class ModelPrimitiveImagery { /** * Creates a new instance * * @param {Model} model The model * @param {ModelRuntimeNode} runtimeNode The node that the primitive is attached to * @param {ModelRuntimePrimitive} runtimePrimitive The primitive * @throws {DeveloperError} If any argument is not defined */ constructor(model, runtimeNode, runtimePrimitive) { //>>includeStart('debug', pragmas.debug); Check.defined("model", model); Check.defined("runtimeNode", runtimeNode); Check.defined("runtimePrimitive", runtimePrimitive); //>>includeEnd('debug'); /** * The model that this instance was created for. * * @type {Model} * @readonly * @private */ this._model = model; /** * The node that the primitive is attached to * * @type {ModelRuntimeNode} * @readonly * @private */ this._runtimeNode = runtimeNode; /** * The primitive that this instance was created for. * * @type {ModelRuntimePrimitive} * @readonly * @private */ this._runtimePrimitive = runtimePrimitive; /** * The <code>MappedPositions</code> objects, one for each ellipsoid * of one of the imagery layers * * These objects are just plain structures that summarize the * cartographic positions of the primitive for one specific * ellipsoid * * @type {MappedPositions[]|undefined} * @private */ this._mappedPositionsPerEllipsoid = undefined; /** * The last <code>model.modelMatrix</code> for which the mapped * positions have been computed. * * This is used for detecting changes in the model matrix that * make it necessary to re-compute the mapped positions. * * @type {Matrix4} * @readonly * @private */ this._mappedPositionsModelMatrix = new Matrix4(); /** * The value that the <code>Cesium3DTileset.imageryLayersModificationCounter</code> * had during the last update call. This is used for triggering updates when the * imagery layer collection in the tileset changes. */ this._lastImageryLayersModificationCounter = 0; /** * The texture coordinate attributes, one for each projection. * * This contains one <code>ModelComponents.Attribute</code> for each * unique projection that is used in the imagery layers. These * texture coordinate attributes are computed based on the mapped * positions for the respective ellipsoid of that projection. */ this._imageryTexCoordAttributesPerProjection = undefined; /** * The current imagery layers. * * This is initialized when the _coveragesPerLayer are computed, * and tracked to that the reference counters of the imageries * can be decreased when the coverages per layer are deleted. * * @type {ImageryLayer[]|undefined} * @private */ this._currentImageryLayers = undefined; /** * Information about the imagery tiles that are covered by the positions * of the primitive. * * This is computed in the <code>update</code> function, based on the * mapped positions of the primitive. After this computation, * <code>_coveragesPerLayer[layerIndex]</code> is an array that contains * the <code>ImageryCoverage</code> objects that describe the imagery * tiles that are covered, including their texture coordinate rectangle. * * @type {ImageryCoverage[][]|undefined} * @private */ this._coveragesPerLayer = undefined; /** * A flag indicating whether all imagery objects that are covered * are "ready". * * This is initially <code>false</code>. During the calls to the * <code>update</code> function (which are triggered from the * <code>Model.update</code> function, each frame), the * <code>_updateImageries</code> function will be called, and * process the imagery tiles, until all them them are in a * state like <code>ImageryState.READY</code>, at which point * this flag is set to <code>true</code>. * * @type {boolean} * @private */ this._allImageriesReady = false; } /** * Returns the <code>ImageryCoverage</code> array that has been * computed for the given imagery layer. * * This assumes that the given imagery layer is part of the * imagery layer collection of the model, and that this * model primitive imagery is "ready", meaning that the * coverages have already been computed. * * Clients may <b>not</b> modify the returned array or any * of its objects! * * @param {ImageryLayer} imageryLayer The imagery layer * @returns {ImageryCoverage[]} The coverage information */ coveragesForImageryLayer(imageryLayer) { const model = this._model; const imageryLayers = model.imageryLayers; const index = imageryLayers.indexOf(imageryLayer); if (index === -1) { throw new DeveloperError("Imagery layer is not part of the model"); } const coveragesPerLayer = this._coveragesPerLayer; if (!defined(coveragesPerLayer)) { throw new DeveloperError( `The coveragesPerLayer have not been computed yet`, ); } return coveragesPerLayer[index]; } /** * Update the state of this instance. * * This is called as part of <code>ModelImagery.update</code>, which in * turn is part of the <code>Model.update</code> that is called in each * frame. * * This will perform the computations that are required to establish * the mapping between the imagery and the primitive. It will... * <ul> * <li> * Compute the <code>MappedPositions</code> of the primitive, * one instance for each ellipsoid * </li> * <li> * Compute the "coverages per layer", containing the information * about which parts of the respective imagery layer are covered * by the mapped positions * </li> * <li> * Compute the texture coordinate attributes for the imagery, one * for each projection, and store them as the * <code>_imageryTexCoordAttributesPerProjection</code> * </li> * <li> * Update the imageries (i.e. processing their state machine by * calling <code>Imagery.processStateMachine</code>) until they * are in the <code>ImageryState.READY</code> state * </li> * </ul> * * @param {FrameState} frameState The frame state */ update(frameState) { //>>includeStart('debug', pragmas.debug); Check.defined("frameState", frameState); //>>includeEnd('debug'); // If the imagery layers have been modified since the last call // to this function, then re-build everything const model = this._model; const content = model.content; const tileset = content.tileset; const modificationCounter = tileset.imageryLayersModificationCounter; if (this._lastImageryLayersModificationCounter !== modificationCounter) { delete this._mappedPositionsPerEllipsoid; this._lastImageryLayersModificationCounter = modificationCounter; } if (this._mappedPositionsNeedUpdate) { model.resetDrawCommands(); this._mappedPositionsPerEllipsoid = this._computeMappedPositionsPerEllipsoid(); this._deleteCoveragesPerLayer(); this._destroyImageryTexCoordAttributes(); } if (!defined(this._imageryTexCoordAttributesPerProjection)) { this._imageryTexCoordAttributesPerProjection = this._computeImageryTexCoordsAttributesPerProjection(); this._uploadImageryTexCoordAttributes(frameState.context); } if (!defined(this._coveragesPerLayer)) { this._computeCoveragesPerLayer(); this._allImageriesReady = false; } if (!this._allImageriesReady) { this._updateImageries(frameState); } } /** * Delete the <code>_coveragesPerLayer</code> if they are defined. * * This will call <code>deleteCoverages</code> for each set of coverages, * and eventually delete the <code>_coveragesPerLayer</code>. * * This will cause the reference counters of the imageries to be * decreased. */ _deleteCoveragesPerLayer() { const coveragesPerLayer = this._coveragesPerLayer; if (!defined(coveragesPerLayer)) { return; } const imageryLayers = this._currentImageryLayers; const length = coveragesPerLayer.length; for (let i = 0; i < length; i++) { const imageryLayer = imageryLayers[i]; const coverages = coveragesPerLayer[i]; this._deleteCoverages(imageryLayer, coverages); } delete this._currentImageryLayers; delete this._coveragesPerLayer; } /** * Delete the given imagery coverage objects for the given imagery * layer, meaning that it will cause the reference counters of the * imageries to be decreased. * * If the imagery layer already has been destroyed, then nothing * will be done. * * @param {ImageryLayer} imageryLayer The imagery layer * @param {ImageryCoverage[]} coverages The coverages */ _deleteCoverages(imageryLayer, coverages) { if (imageryLayer.isDestroyed()) { return; } const length = coverages.length; for (let i = 0; i < length; i++) { const coverage = coverages[i]; const imagery = coverage.imagery; imagery.releaseReference(); } } /** * Create the GPU buffers for the typed arrays that are contained * in the <code>_imageryTexCoordAttributesPerProjection</code> * * @param {Context} context The GL context */ _uploadImageryTexCoordAttributes(context) { //>>includeStart('debug', pragmas.debug); Check.defined("context", context); //>>includeEnd('debug'); const attributes = this._imageryTexCoordAttributesPerProjection; if (!defined(attributes)) { return; } const n = attributes.length; for (let i = 0; i < n; i++) { const attribute = attributes[i]; // Allocate the GL resources for the new attribute const imageryTexCoordBuffer = Buffer.createVertexBuffer({ context: context, typedArray: attribute.typedArray, usage: BufferUsage.STATIC_DRAW, }); // TODO_DRAPING Review this. Probably, some cleanup // has to happen somewhere else after setting this. // Check that the call to "destroy" in // _destroyImageryTexCoordAttributes is the right // thing to do here. imageryTexCoordBuffer.vertexArrayDestroyable = false; attribute.buffer = imageryTexCoordBuffer; } } /** * Destroy the <code>_imageryTexCoordAttributesPerProjection</code> * array. * * This is called for cleaning up the allocated GPU resources, before * they are supposed to be re-computed with * <code>_computeImageryTexCoordsAttributesPerProjection</code> */ _destroyImageryTexCoordAttributes() { const attributes = this._imageryTexCoordAttributesPerProjection; if (!defined(attributes)) { return; } const n = attributes.length; for (let i = 0; i < n; i++) { const attribute = attributes[i]; if (defined(attribute)) { if (defined(attribute.buffer)) { if (!attribute.buffer.isDestroyed()) { attribute.buffer.destroy(); } } attributes[i] = undefined; } } delete this._imageryTexCoordAttributesPerProjection; } /** * Returns whether the <code>MappedPositions</code> have to be * re-computed with <code>_computeMappedPositionsPerEllipsoid</code>. * * This is <code>true</code> when the positions have not yet been * computed, or when the <code>modelMatrix</code> of the model * changed since the previous call. * * @returns {boolean} Whether the mapped positions need an update * @private */ get _mappedPositionsNeedUpdate() { if (!defined(this._mappedPositionsPerEllipsoid)) { return true; } const model = this._model; const lastModelMatrix = this._mappedPositionsModelMatrix; if (!Matrix4.equals(model.modelMatrix, lastModelMatrix)) { return true; } return false; } /** * Computes the mapped positions of the primitive, one for each ellipsoid. * * This computes the <i>unique</i> ellipsoids that appear in the imagery * layers of the model, and creates one <code>MappedPositions</code> * object for each of them. * * The respective <code>MappedPositions</code> objects will contain * the cartographic positions that are computed from the positions * of the primitive. These will serve as the basis for computing the * part of the imagery that is covered by the primitive. * * These mapped positions depend on the current <code>modelMatrix</code> * of the model. So they have to be re-computed when the model matrix * changes. * * @returns {MappedPositions[]} The mapped positions * @private */ _computeMappedPositionsPerEllipsoid() { const model = this._model; const runtimeNode = this._runtimeNode; const runtimePrimitive = this._runtimePrimitive; const primitivePositionAttribute = ModelPrimitiveImagery._obtainPrimitivePositionAttribute( runtimePrimitive.primitive, ); const numPositions = primitivePositionAttribute.count; const primitivePositionTransform = ModelPrimitiveImagery._computePrimitivePositionTransform( model, runtimeNode, undefined, ); const mappedPositionsPerEllipsoid = []; const ellipsoids = ModelPrimitiveImagery._computeUniqueEllipsoids( model.imageryLayers, ); const length = ellipsoids.length; for (let i = 0; i < length; i++) { const ellipsoid = ellipsoids[i]; const cartographicPositions = ModelImageryMapping.createCartographicPositions( primitivePositionAttribute, primitivePositionTransform, ellipsoid, ); const cartographicBoundingRectangle = ModelImageryMapping.computeCartographicBoundingRectangle( cartographicPositions, ); const mappedPositions = new MappedPositions( cartographicPositions, numPositions, cartographicBoundingRectangle, ellipsoid, ); mappedPositionsPerEllipsoid.push(mappedPositions); } Matrix4.clone(model.modelMatrix, this._mappedPositionsModelMatrix); return mappedPositionsPerEllipsoid; } /** * Computes an array containing the <i>unique</i> ellipsoids that * appear in the imagery layers of the given collection. * * @param {ImageryLayerCollection} imageryLayers * @returns {Ellipsoid[]} The ellipsoids * @private */ static _computeUniqueEllipsoids(imageryLayers) { //>>includeStart('debug', pragmas.debug); Check.defined("imageryLayers", imageryLayers); //>>includeEnd('debug'); const ellipsoidsSet = new Set(); const length = imageryLayers.length; for (let i = 0; i < length; i++) { const imageryLayer = imageryLayers.get(i); const ellipsoid = ModelPrimitiveImagery._getEllipsoid(imageryLayer); ellipsoidsSet.add(ellipsoid); } return [...ellipsoidsSet]; } /** * Computes one coordinate attribute for each unique projection * that is used in the imagery layers. * * This is taking the mapped positions, projecting them with * the respective projection, and creating a texture coordinate * attribute that describes the texture coordinates of these * positions, relative to the cartographic bounding rectangle * of the mapped positions. * * @returns {ModelComponents.Attribute[]} The attributes */ _computeImageryTexCoordsAttributesPerProjection() { const model = this._model; const imageryLayers = model.imageryLayers; // Compute the arrays containing ALL projections and the array // containing the UNIQUE projections from the imagery layers. // Texture coordinate attributes only have to be created once // for each projection. const allProjections = ModelPrimitiveImagery._extractProjections(imageryLayers); const uniqueProjections = [...new Set(allProjections)]; // Create one texture coordinate attribute for each distinct // projection that is used in the imagery layers const attributes = this._createImageryTexCoordAttributes(uniqueProjections); return attributes; } /** * Computes an array containing the projections that are used in * the given imagery layers. * * (Note that this array may contain duplicates) * * @param {ImageryLayerCollection} imageryLayers The imagery layers * @returns {MapProjection[]} The projections * @private */ static _extractProjections(imageryLayers) { //>>includeStart('debug', pragmas.debug); Check.defined("imageryLayers", imageryLayers); //>>includeEnd('debug'); const projections = []; const length = imageryLayers.length; for (let i = 0; i < length; i++) { const imageryLayer = imageryLayers.get(i); const projection = ModelPrimitiveImagery._getProjection(imageryLayer); projections.push(projection); } return projections; } /** * Returns the projection of the given imagery layer. * * This only exists to hide a train wreck * * @param {ImageryLayer} imageryLayer The imagery layer * @returns {MapProjection} The projection * @private */ static _getProjection(imageryLayer) { //>>includeStart('debug', pragmas.debug); Check.defined("imageryLayer", imageryLayer); //>>includeEnd('debug'); const projection = imageryLayer.imageryProvider.tilingScheme.projection; return projection; } /** * Create texture coordinates, one for each projection. * * This will create a texture coordinate attribute for each of the given projections, * using <code>ModelImageryMapping.createTextureCoordinatesAttributeForMappedPositions</code>, * * (This means that the given projections should indeed be unique, * i.e. contain no duplicates) * * @param {MapProjection[]} uniqueProjections The projections * @returns {ModelComponents.Attribute[]} The attributes */ _createImageryTexCoordAttributes(uniqueProjections) { //>>includeStart('debug', pragmas.debug); Check.defined("uniqueProjections", uniqueProjections); //>>includeEnd('debug'); const imageryTexCoordAttributePerUniqueProjection = []; const length = uniqueProjections.length; for (let i = 0; i < length; i++) { // Obtain the mapped positions for the ellipsoid that is used // in the projection (i.e. the cartographic positions of the // primitive, for the respective ellipsoid) const projection = uniqueProjections[i]; const ellipsoid = projection.ellipsoid; const mappedPositions = this.mappedPositionsForEllipsoid(ellipsoid); // Create the actual attribute const imageryTexCoordAttribute = ModelImageryMapping.createTextureCoordinatesAttributeForMappedPositions( mappedPositions, projection, ); imageryTexCoordAttributePerUniqueProjection.push( imageryTexCoordAttribute, ); } return imageryTexCoordAttributePerUniqueProjection; } /** * Compute the coverage information for the primitive, based on the * imagery layers that are associated with the model. * * This updates the <code>_coveragesPerLayer[layerIndex]</code>, which * is an array that contains the <code>ImageryCoverage</code> objects that * describe the imagery tiles that are covered by the primitive, including * their texture coordinate rectangle. * * This has to be called after the mapped positions for the primitive * have been computed with <code>_computeMappedPositionsPerEllipsoid</code>. * * @private */ _computeCoveragesPerLayer() { const coveragesPerLayer = []; const currentImageryLayers = []; const model = this._model; const imageryLayers = model.imageryLayers; const length = imageryLayers.length; for (let i = 0; i < length; i++) { const imageryLayer = imageryLayers.get(i); const coverages = this._computeCoverage(imageryLayer); coveragesPerLayer.push(coverages); currentImageryLayers.push(imageryLayer); } this._coveragesPerLayer = coveragesPerLayer; this._currentImageryLayers = currentImageryLayers; } /** * Compute the coverage information for the primitive, based on the * given imagery layer. * * This returns an array that contains the <code>ImageryCoverage</code> * objects that describe the imagery tiles that are covered by the * primitive, including their texture coordinate rectangle. * * This has to be called after the mapped positions for the primitive * have been computed with <code>_computeMappedPositionsPerEllipsoid</code>. * * @param {ImageryLayer} imageryLayer The imagery layer * @returns {ImageryCoverage[]} The coverage information * @private */ _computeCoverage(imageryLayer) { const mappedPositions = this.mappedPositionsForImageryLayer(imageryLayer); const cartographicBoundingRectangle = mappedPositions.cartographicBoundingRectangle; const imageryLevel = this._computeImageryLevel( imageryLayer, cartographicBoundingRectangle, ); const coverages = ImageryCoverage.createImageryCoverages( cartographicBoundingRectangle, imageryLayer, imageryLevel, ); return coverages; } /** * Compute a <code>level</code> for accessing the imagery from the given * imagery layer that is suitable for a primitive with the given bounding * rectangle. * * @param {ImageryLayer} imageryLayer The imagery layer * @param {Rectangle} cartographicBoundingRectangle The cartographic * bounding rectangle, as obtained from the MappedPositions for * the given imagery layer * @returns {number} The imagery level */ _computeImageryLevel(imageryLayer, cartographicBoundingRectangle) { const imageryProvider = imageryLayer.imageryProvider; const tilingScheme = imageryProvider.tilingScheme; const rectangle = tilingScheme.rectangle; // The number of tiles covered by the boundingRectangle (b) // for a certain level, based on the tiling scheme rectangle (r) is // numberOfTilesCovered = b / (r / 2^level) // Solving for "level" yields // level = log2( numberOfTilesCovered * r / b) // The goal here is to drape approximately (!) one imagery // tile on each primitive. In practice, it may be more // (up to 9 in theory) const desiredNumberOfTilesCovered = 1; // Perform the computation of the desired level, based on the // number of tiles that should be covered (by whatever is // larger, the width or the height) let boundingRectangleSize = cartographicBoundingRectangle.width; let rectangleSize = rectangle.width; if ( cartographicBoundingRectangle.height > cartographicBoundingRectangle.width ) { boundingRectangleSize = cartographicBoundingRectangle.height; rectangleSize = rectangle.height; } const desiredLevel = Math.log2( (desiredNumberOfTilesCovered * rectangleSize) / boundingRectangleSize, ); // Clamp the level to a valid range, and an integer value const imageryLevel = ImageryCoverage._clampImageryLevel( imageryProvider, desiredLevel, ); return imageryLevel; } /** * Update all <code>Imagery</code> objects. * * This is called as part of <code>update</code>, until all required * imagery tiles are "ready", as indicated by their <code>state</code> * being <code>ImageryState.READY</code>. * * This is called after it has been determined which imagery tiles are * covered by the primitive (i.e. after the <code>_coveragesPerLayer</code> * have been computed by calling <code>_computeCoverages</code>). * * For each covered imagery tile, this will call * <code>Imagery.processStateMachine</code> until they are either * READY, FAILED, or INVALID. * * Once they all are in one of these final states, it will set the * <code>_allImageriesReady</code> flag to <code>true</code>. * * @param {FrameState} frameState The frame state, to be passed to * <code>imagery.processStateMachine</code> * @private */ _updateImageries(frameState) { const model = this._model; const coveragesPerLayer = this._coveragesPerLayer; const length = coveragesPerLayer.length; let allImageriesReady = true; for (let i = 0; i < length; i++) { const coverages = coveragesPerLayer[i]; const n = coverages.length; for (let j = 0; j < n; j++) { const coverage = coverages[j]; const imagery = coverage.imagery; // In the context of loading the imagery for draping // it over the primitive, the imagery counts as "ready" // when it is really ready, but also when it failed // or was invalid (otherwise, the primitive would // never turn "ready" const countsAsReady = imagery.state === ImageryState.READY || imagery.state === ImageryState.FAILED || imagery.state === ImageryState.INVALID; if (!countsAsReady) { allImageriesReady = false; imagery.processStateMachine(frameState, false, false); } } } // When the imageries turned ready, reset the draw commands // to trigger a rendering with the updated draw commands // that include the imagery now. if (allImageriesReady) { model.resetDrawCommands(); } this._allImageriesReady = allImageriesReady; } /** * Returns the <code>MappedPositions</code> object that contains * information about the primitive positions that have been computed * for the given imagery layer. * * This assumes that <code>_computeMappedPositionsPerEllipsoid</code> has * already been called. * * @param {ImageryLayer} imageryLayer The imageryLayer * @returns {MappedPositions} The mapped positions * @throws {DeveloperError} If the mapped positions for the * ellipsoid could not be found. */ mappedPositionsForImageryLayer(imageryLayer) { const ellipsoid = ModelPrimitiveImagery._getEllipsoid(imageryLayer); return this.mappedPositionsForEllipsoid(ellipsoid); } /** * Returns the <code>MappedPositions</code> object that contains * information about the primitive positions that have been computed * from the given ellipsoid. * * This assumes that <code>_computeMappedPositions</code> has * already been called. * * @param {Ellipsoid} ellipsoid The ellipsoid * @returns {MappedPositions} The mapped positions * @throws {DeveloperError} If the mapped positions for the * given ellipsoid could not be found. */ mappedPositionsForEllipsoid(ellipsoid) { const mappedPositionsPerEllipsoid = this._mappedPositionsPerEllipsoid; if (!defined(mappedPositionsPerEllipsoid)) { throw new DeveloperError( `The mappedPositions have not been computed yet`, ); } const length = mappedPositionsPerEllipsoid.length; for (let i = 0; i < length; i++) { const mappedPositions = mappedPositionsPerEllipsoid[i]; if (mappedPositions.ellipsoid === ellipsoid) { return mappedPositions; } } throw new DeveloperError( `Could not find mapped positions for ellipsoid ${ellipsoid}`, ); } /** * Returns the texture coordinate attributes for the primitive that * are used for draping the imagery. * * This will be available when this object is <code>ready</code>, and * will contain one attribute for each unique projection that appears * in the imagery layers. * * @returns {ModelComponents.Attribute[]} The attributes */ imageryTexCoordAttributesPerProjection() { const imageryTexCoordAttributesPerProjection = this._imageryTexCoordAttributesPerProjection; if (!defined(imageryTexCoordAttributesPerProjection)) { throw new DeveloperError( `The imagery texture coordinate attributes have not been computed yet`, ); } return this._imageryTexCoordAttributesPerProjection; } /** * Returns whether the draping computations are "ready". * * This means that the <code>coveragesPerLayer</code> information * has been computed, which describes the set of imagery tiles * that are covered by the primitive, <b>and</b> that all the * covered imagery tiles are in a state that counts as "ready" * (i.e. <code>ImageryState.READY</code>, <code>FAILED</code>, * or <code>INVALID</code>). * * @returns {boolean} Whether the draping computations are ready */ get ready() { const coveragesPerLayer = this._coveragesPerLayer; if (!defined(coveragesPerLayer)) { return false; } return this._allImageriesReady; } /** * Returns whether this object was destroyed. * * If this object was destroyed, calling any function other than * <code>isDestroyed</code> 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._deleteCoveragesPerLayer(); this._destroyImageryTexCoordAttributes(); return destroyObject(this); } /** * Compute the transform that apparently has to be applied to * the positions attribute of a primitive, to compute the * actual, final positions in ECEF coordinates. * * This is based on the computation of the bounding * sphere that is done at the end of buildDrawCommands * * @param {Model} model The model * @param {ModelComponents.Node} runtimeNode The runtime node * that the primitive is attached to * @param {Matrix4} [result] The result * @returns {Matrix4} The result * @private */ static _computePrimitivePositionTransform(model, runtimeNode, result) { //>>includeStart('debug', pragmas.debug); Check.defined("model", model); Check.defined("runtimeNode", runtimeNode); //>>includeEnd('debug'); if (!defined(result)) { result = new Matrix4(); } const modelSceneGraph = model.sceneGraph; Matrix4.clone(Matrix4.IDENTITY, result); Matrix4.multiply(result, model.modelMatrix, result); Matrix4.multiply(result, modelSceneGraph.components.transform, result); Matrix4.multiply(result, modelSceneGraph.axisCorrectionMatrix, result); Matrix4.multiply(result, runtimeNode.computedTransform, result); return result; } /** * Returns the <code>"POSITION"</code> attribute from the given primitive. * * The <code>"POSITION"</code> attribute is required. If it is not * defined for the given primitive, then a <code>DeveloperError</code> * is thrown. * * @param {ModelComponents.Primitive} primitive The primitive * @returns {ModelComponents.Attribute} The position attribute * @throws {DeveloperError} If there is no position attribute * @private */ static _obtainPrimitivePositionAttribute(primitive) { //>>includeStart('debug', pragmas.debug); Check.defined("primitive", primitive); //>>includeEnd('debug'); const primitivePositionAttribute = ModelUtility.getAttributeBySemantic( primitive, "POSITION", ); if (!defined(primitivePositionAttribute)) { throw new DeveloperError( "The primitive does not have a POSITION attribute", ); } return primitivePositionAttribute; } /** * Returns the ellipsoid of the given imagery layer. * * This only exists to hide a train wreck * * @param {ImageryLayer} imageryLayer The imagery layer * @returns {Ellipsoid} The ellipsoid * @private */ static _getEllipsoid(imageryLayer) { const ellipsoid = imageryLayer.imageryProvider.tilingScheme.projection.ellipsoid; return ellipsoid; } } export default ModelPrimitiveImagery;