Add existing to tracked
This commit is contained in:
+972
@@ -0,0 +1,972 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user