UNPKG

@cesium/engine

Version:

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

552 lines (474 loc) 15.4 kB
import Color from "../../Core/Color.js"; import combine from "../../Core/combine.js"; import defined from "../../Core/defined.js"; import destroyObject from "../../Core/destroyObject.js"; import DeveloperError from "../../Core/DeveloperError.js"; import Ellipsoid from "../../Core/Ellipsoid.js"; import Pass from "../../Renderer/Pass.js"; import ModelAnimationLoop from "../ModelAnimationLoop.js"; import Model from "./Model.js"; /** * Represents the contents of a glTF, glb or * {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel|Batched 3D Model} * tile in a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles} tileset. * <p> * Implements the {@link Cesium3DTileContent} interface. * </p> * This object is normally not instantiated directly, use {@link Model3DTileContent.fromGltf}, {@link Model3DTileContent.fromB3dm}, {@link Model3DTileContent.fromI3dm}, {@link Model3DTileContent.fromPnts}, or {@link Model3DTileContent.fromGeoJson}. * * @alias Model3DTileContent * @constructor * @private */ function Model3DTileContent(tileset, tile, resource) { this._tileset = tileset; this._tile = tile; this._resource = resource; this._model = undefined; this._metadata = undefined; this._group = undefined; this._ready = false; } Object.defineProperties(Model3DTileContent.prototype, { featuresLength: { get: function () { const model = this._model; const featureTables = model.featureTables; const featureTableId = model.featureTableId; if (defined(featureTables) && defined(featureTables[featureTableId])) { return featureTables[featureTableId].featuresLength; } return 0; }, }, pointsLength: { get: function () { return this._model.statistics.pointsLength; }, }, trianglesLength: { get: function () { return this._model.statistics.trianglesLength; }, }, geometryByteLength: { get: function () { return this._model.statistics.geometryByteLength; }, }, texturesByteLength: { get: function () { return this._model.statistics.texturesByteLength; }, }, batchTableByteLength: { get: function () { const statistics = this._model.statistics; return ( statistics.propertyTablesByteLength + statistics.batchTexturesByteLength ); }, }, innerContents: { get: function () { return undefined; }, }, /** * Returns true when the tile's content is ready to render; otherwise false * * @memberof Model3DTileContent.prototype * * @type {boolean} * @readonly * @private */ ready: { get: function () { return this._ready; }, }, tileset: { get: function () { return this._tileset; }, }, tile: { get: function () { return this._tile; }, }, url: { get: function () { return this._resource.getUrlComponent(true); }, }, batchTable: { get: function () { const model = this._model; const featureTables = model.featureTables; const featureTableId = model.featureTableId; if (defined(featureTables) && defined(featureTables[featureTableId])) { return featureTables[featureTableId]; } return undefined; }, }, metadata: { get: function () { return this._metadata; }, set: function (value) { this._metadata = value; }, }, group: { get: function () { return this._group; }, set: function (value) { this._group = value; }, }, }); /** * Returns an array containing the `texture.id` values for all textures * that are part of this content. * * @returns {string[]} The texture IDs */ Model3DTileContent.prototype.getTextureIds = function () { return this._model.statistics.getTextureIds(); }; /** * Returns the length, in bytes, of the texture data for the texture with * the given ID that is part of this content, or `undefined` if this * content does not contain the texture with the given ID. * * @param {string} textureId The texture ID * @returns {number|undefined} The texture byte length */ Model3DTileContent.prototype.getTextureByteLengthById = function (textureId) { return this._model.statistics.getTextureByteLengthById(textureId); }; /** * Returns the object that was created for the given extension. * * The given name may be the name of a glTF extension, like `"EXT_example_extension"`. * If the specified extension was present in the root of the underlying glTF asset, * and a loader for the specified extension has processed the extension data, then * this will return the model representation of the extension. * * @param {string} extensionName The name of the extension * @returns {object|undefined} The object, or `undefined` * * @private */ Model3DTileContent.prototype.getExtension = function (extensionName) { const model = this._model; const extension = model.getExtension(extensionName); return extension; }; Model3DTileContent.prototype.getFeature = function (featureId) { const model = this._model; const featureTableId = model.featureTableId; //>>includeStart('debug', pragmas.debug); if (!defined(featureTableId)) { throw new DeveloperError( "No feature ID set is selected. Make sure Cesium3DTileset.featureIdLabel or Cesium3DTileset.instanceFeatureIdLabel is defined", ); } //>>includeEnd('debug'); const featureTable = model.featureTables[featureTableId]; //>>includeStart('debug', pragmas.debug); if (!defined(featureTable)) { throw new DeveloperError( "No feature table found for the selected feature ID set", ); } //>>includeEnd('debug'); //>>includeStart('debug', pragmas.debug); const featuresLength = featureTable.featuresLength; if (!defined(featureId) || featureId < 0 || featureId >= featuresLength) { throw new DeveloperError( `featureId is required and must be between 0 and featuresLength - 1 (${ featuresLength - 1 }).`, ); } //>>includeEnd('debug'); return featureTable.getFeature(featureId); }; Model3DTileContent.prototype.hasProperty = function (featureId, name) { const model = this._model; const featureTableId = model.featureTableId; if (!defined(featureTableId)) { return false; } const featureTable = model.featureTables[featureTableId]; return featureTable.hasProperty(featureId, name); }; Model3DTileContent.prototype.applyDebugSettings = function (enabled, color) { color = enabled ? color : Color.WHITE; if (this.featuresLength === 0) { this._model.color = color; } else if (defined(this.batchTable)) { this.batchTable.setAllColor(color); } }; Model3DTileContent.prototype.applyStyle = function (style) { // the setter will call model.applyStyle() this._model.style = style; }; Model3DTileContent.prototype.update = function (tileset, frameState) { const model = this._model; const tile = this._tile; model.colorBlendAmount = tileset.colorBlendAmount; model.colorBlendMode = tileset.colorBlendMode; model.modelMatrix = tile.computedTransform; model.customShader = tileset.customShader; model.featureIdLabel = tileset.featureIdLabel; model.instanceFeatureIdLabel = tileset.instanceFeatureIdLabel; model.lightColor = tileset.lightColor; model.imageBasedLighting = tileset.imageBasedLighting; model.backFaceCulling = tileset.backFaceCulling; model.shadows = tileset.shadows; model.showCreditsOnScreen = tileset.showCreditsOnScreen; model.splitDirection = tileset.splitDirection; model.debugWireframe = tileset.debugWireframe; model.showOutline = tileset.showOutline; model.outlineColor = tileset.outlineColor; model.pointCloudShading = tileset.pointCloudShading; // Updating clipping planes requires more effort because of ownership checks const tilesetClippingPlanes = tileset.clippingPlanes; model.referenceMatrix = tileset.clippingPlanesOriginMatrix; if (defined(tilesetClippingPlanes) && tile.clippingPlanesDirty) { // Dereference the clipping planes from the model if they are irrelevant. model._clippingPlanes = tilesetClippingPlanes.enabled && tile._isClipped ? tilesetClippingPlanes : undefined; } const tilesetEnvironmentMapManager = tileset.environmentMapManager; if (model.environmentMapManager !== tilesetClippingPlanes) { model._environmentMapManager = tilesetEnvironmentMapManager; } // If the model references a different ClippingPlaneCollection from the tileset, // update the model to use the new ClippingPlaneCollection. if ( defined(tilesetClippingPlanes) && defined(model._clippingPlanes) && model._clippingPlanes !== tilesetClippingPlanes ) { model._clippingPlanes = tilesetClippingPlanes; model._clippingPlanesState = 0; } // Updating clipping polygons requires more effort because of ownership checks const tilesetClippingPolygons = tileset.clippingPolygons; if (defined(tilesetClippingPolygons) && tile.clippingPolygonsDirty) { // Dereference the clipping polygons from the model if they are irrelevant. model._clippingPolygons = tilesetClippingPolygons.enabled && tile._isClippedByPolygon ? tilesetClippingPolygons : undefined; } // If the model references a different ClippingPolygonCollection from the tileset, // update the model to use the new ClippingPolygonCollection. if ( defined(tilesetClippingPolygons) && defined(model._clippingPolygons) && model._clippingPolygons !== tilesetClippingPolygons ) { model._clippingPolygons = tilesetClippingPolygons; model._clippingPolygonsState = 0; } model.update(frameState); if (!this._ready && model.ready) { // Animation can only be added once the model is ready model.activeAnimations.addAll({ loop: ModelAnimationLoop.REPEAT, }); this._ready = true; } }; Model3DTileContent.prototype.isDestroyed = function () { return false; }; Model3DTileContent.prototype.destroy = function () { this._model = this._model && this._model.destroy(); return destroyObject(this); }; Model3DTileContent.fromGltf = async function (tileset, tile, resource, gltf) { const content = new Model3DTileContent(tileset, tile, resource); const additionalOptions = { gltf: gltf, basePath: resource, }; const modelOptions = makeModelOptions( tileset, tile, content, additionalOptions, ); const classificationType = tileset.vectorClassificationOnly ? undefined : tileset.classificationType; modelOptions.classificationType = classificationType; const model = await Model.fromGltfAsync(modelOptions); content._model = model; return content; }; Model3DTileContent.fromB3dm = async function ( tileset, tile, resource, arrayBuffer, byteOffset, ) { const content = new Model3DTileContent(tileset, tile, resource); const additionalOptions = { arrayBuffer: arrayBuffer, byteOffset: byteOffset, resource: resource, }; const modelOptions = makeModelOptions( tileset, tile, content, additionalOptions, ); const classificationType = tileset.vectorClassificationOnly ? undefined : tileset.classificationType; modelOptions.classificationType = classificationType; const model = await Model.fromB3dm(modelOptions); content._model = model; return content; }; Model3DTileContent.fromI3dm = async function ( tileset, tile, resource, arrayBuffer, byteOffset, ) { const content = new Model3DTileContent(tileset, tile, resource); const additionalOptions = { arrayBuffer: arrayBuffer, byteOffset: byteOffset, resource: resource, }; const modelOptions = makeModelOptions( tileset, tile, content, additionalOptions, ); const model = await Model.fromI3dm(modelOptions); content._model = model; return content; }; Model3DTileContent.fromPnts = async function ( tileset, tile, resource, arrayBuffer, byteOffset, ) { const content = new Model3DTileContent(tileset, tile, resource); const additionalOptions = { arrayBuffer: arrayBuffer, byteOffset: byteOffset, resource: resource, }; const modelOptions = makeModelOptions( tileset, tile, content, additionalOptions, ); const model = await Model.fromPnts(modelOptions); content._model = model; return content; }; Model3DTileContent.fromGeoJson = async function ( tileset, tile, resource, geoJson, ) { const content = new Model3DTileContent(tileset, tile, resource); const additionalOptions = { geoJson: geoJson, resource: resource, }; const modelOptions = makeModelOptions( tileset, tile, content, additionalOptions, ); const model = await Model.fromGeoJson(modelOptions); content._model = model; return content; }; /** * Find an intersection between a ray and the tile content surface that was rendered. The ray must be given in world coordinates. * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. * @param {Cartesian3|undefined} [result] The intersection or <code>undefined</code> if none was found. * @returns {Cartesian3|undefined} The intersection or <code>undefined</code> if none was found. * * @private */ Model3DTileContent.prototype.pick = function (ray, frameState, result) { if (!defined(this._model) || !this._ready) { return undefined; } const verticalExaggeration = frameState.verticalExaggeration; const relativeHeight = frameState.verticalExaggerationRelativeHeight; // All tilesets assume a WGS84 ellipsoid return this._model.pick( ray, frameState, verticalExaggeration, relativeHeight, Ellipsoid.WGS84, result, ); }; function makeModelOptions(tileset, tile, content, additionalOptions) { const mainOptions = { cull: false, // The model is already culled by 3D Tiles releaseGltfJson: true, // Models are unique and will not benefit from caching so save memory opaquePass: Pass.CESIUM_3D_TILE, // Draw opaque portions of the model during the 3D Tiles pass modelMatrix: tile.computedTransform, upAxis: tileset._modelUpAxis, forwardAxis: tileset._modelForwardAxis, incrementallyLoadTextures: false, customShader: tileset.customShader, content: content, colorBlendMode: tileset.colorBlendMode, colorBlendAmount: tileset.colorBlendAmount, lightColor: tileset.lightColor, imageBasedLighting: tileset.imageBasedLighting, featureIdLabel: tileset.featureIdLabel, instanceFeatureIdLabel: tileset.instanceFeatureIdLabel, pointCloudShading: tileset.pointCloudShading, clippingPlanes: tileset.clippingPlanes, backFaceCulling: tileset.backFaceCulling, shadows: tileset.shadows, showCreditsOnScreen: tileset.showCreditsOnScreen, splitDirection: tileset.splitDirection, enableDebugWireframe: tileset._enableDebugWireframe, debugWireframe: tileset.debugWireframe, projectTo2D: tileset._projectTo2D, enablePick: tileset._enablePick, enableShowOutline: tileset._enableShowOutline, showOutline: tileset.showOutline, outlineColor: tileset.outlineColor, }; return combine(additionalOptions, mainOptions); } export default Model3DTileContent;