UNPKG

cesium

Version:

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

1,425 lines (1,304 loc) 194 kB
import BoundingSphere from "../Core/BoundingSphere.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartesian4 from "../Core/Cartesian4.js"; import Cartographic from "../Core/Cartographic.js"; import Check from "../Core/Check.js"; import clone from "../Core/clone.js"; import Color from "../Core/Color.js"; import combine from "../Core/combine.js"; import createGuid from "../Core/createGuid.js"; import Credit from "../Core/Credit.js"; import defaultValue from "../Core/defaultValue.js"; import defer from "../Core/defer.js"; import defined from "../Core/defined.js"; import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import DistanceDisplayCondition from "../Core/DistanceDisplayCondition.js"; import FeatureDetection from "../Core/FeatureDetection.js"; import getAbsoluteUri from "../Core/getAbsoluteUri.js"; import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js"; import getMagic from "../Core/getMagic.js"; import getStringFromTypedArray from "../Core/getStringFromTypedArray.js"; import IndexDatatype from "../Core/IndexDatatype.js"; import ImageBasedLighting from "./ImageBasedLighting.js"; import loadImageFromTypedArray from "../Core/loadImageFromTypedArray.js"; import loadKTX2 from "../Core/loadKTX2.js"; import CesiumMath from "../Core/Math.js"; import Matrix3 from "../Core/Matrix3.js"; import Matrix4 from "../Core/Matrix4.js"; import PixelFormat from "../Core/PixelFormat.js"; import PrimitiveType from "../Core/PrimitiveType.js"; import Quaternion from "../Core/Quaternion.js"; import Resource from "../Core/Resource.js"; import Transforms from "../Core/Transforms.js"; import WebGLConstants from "../Core/WebGLConstants.js"; import Buffer from "../Renderer/Buffer.js"; import BufferUsage from "../Renderer/BufferUsage.js"; import DrawCommand from "../Renderer/DrawCommand.js"; import Pass from "../Renderer/Pass.js"; import RenderState from "../Renderer/RenderState.js"; import Sampler from "../Renderer/Sampler.js"; import ShaderProgram from "../Renderer/ShaderProgram.js"; import ShaderSource from "../Renderer/ShaderSource.js"; import Texture from "../Renderer/Texture.js"; import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js"; import TextureWrap from "../Renderer/TextureWrap.js"; import VertexArray from "../Renderer/VertexArray.js"; import addDefaults from "./GltfPipeline/addDefaults.js"; import addPipelineExtras from "./GltfPipeline/addPipelineExtras.js"; import ForEach from "./GltfPipeline/ForEach.js"; import getAccessorByteStride from "./GltfPipeline/getAccessorByteStride.js"; import usesExtension from "./GltfPipeline/usesExtension.js"; import numberOfComponentsForType from "./GltfPipeline/numberOfComponentsForType.js"; import parseGlb from "./GltfPipeline/parseGlb.js"; import updateVersion from "./GltfPipeline/updateVersion.js"; import Axis from "./Axis.js"; import BlendingState from "./BlendingState.js"; import ClippingPlaneCollection from "./ClippingPlaneCollection.js"; import ColorBlendMode from "./ColorBlendMode.js"; import DepthFunction from "./DepthFunction.js"; import DracoLoader from "./DracoLoader.js"; import getClipAndStyleCode from "./getClipAndStyleCode.js"; import getClippingFunction from "./getClippingFunction.js"; import HeightReference from "./HeightReference.js"; import JobType from "./JobType.js"; import ModelAnimationCache from "./ModelAnimationCache.js"; import ModelAnimationCollection from "./ModelAnimationCollection.js"; import ModelLoadResources from "./ModelLoadResources.js"; import ModelMaterial from "./ModelMaterial.js"; import ModelMesh from "./ModelMesh.js"; import ModelNode from "./ModelNode.js"; import ModelOutlineLoader from "./ModelOutlineLoader.js"; import ModelUtility from "./ModelUtility.js"; import OctahedralProjectedCubeMap from "./OctahedralProjectedCubeMap.js"; import processModelMaterialsCommon from "./processModelMaterialsCommon.js"; import processPbrMaterials from "./processPbrMaterials.js"; import SceneMode from "./SceneMode.js"; import ShadowMode from "./ShadowMode.js"; import SplitDirection from "./SplitDirection.js"; import Splitter from "./Splitter.js"; import StencilConstants from "./StencilConstants.js"; const boundingSphereCartesian3Scratch = new Cartesian3(); const ModelState = ModelUtility.ModelState; // glTF MIME types discussed in https://github.com/KhronosGroup/glTF/issues/412 and https://github.com/KhronosGroup/glTF/issues/943 const defaultModelAccept = "model/gltf-binary,model/gltf+json;q=0.8,application/json;q=0.2,*/*;q=0.01"; const articulationEpsilon = CesiumMath.EPSILON16; /////////////////////////////////////////////////////////////////////////// function setCachedGltf(model, cachedGltf) { model._cachedGltf = cachedGltf; } // glTF JSON can be big given embedded geometry, textures, and animations, so we // cache it across all models using the same url/cache-key. This also reduces the // slight overhead in assigning defaults to missing values. // // Note that this is a global cache, compared to renderer resources, which // are cached per context. function CachedGltf(options) { this._gltf = options.gltf; this.ready = options.ready; this.modelsToLoad = []; this.count = 0; } Object.defineProperties(CachedGltf.prototype, { gltf: { set: function (value) { this._gltf = value; }, get: function () { return this._gltf; }, }, }); CachedGltf.prototype.makeReady = function (gltfJson) { this.gltf = gltfJson; const models = this.modelsToLoad; const length = models.length; for (let i = 0; i < length; ++i) { const m = models[i]; if (!m.isDestroyed()) { setCachedGltf(m, this); } } this.modelsToLoad = undefined; this.ready = true; }; const gltfCache = {}; const uriToGuid = {}; /////////////////////////////////////////////////////////////////////////// /** * A 3D model based on glTF, the runtime asset format for WebGL, OpenGL ES, and OpenGL. * <p> * Cesium includes support for geometry and materials, glTF animations, and glTF skinning. * In addition, individual glTF nodes are pickable with {@link Scene#pick} and animatable * with {@link Model#getNode}. glTF cameras and lights are not currently supported. * </p> * <p> * An external glTF asset is created with {@link Model.fromGltf}. glTF JSON can also be * created at runtime and passed to this constructor function. In either case, the * {@link Model#readyPromise} is resolved when the model is ready to render, i.e., * when the external binary, image, and shader files are downloaded and the WebGL * resources are created. * </p> * <p> * Cesium supports glTF assets with the following extensions: * <ul> * <li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Khronos/KHR_binary_glTF/README.md|KHR_binary_glTF (glTF 1.0)} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Khronos/KHR_materials_common/README.md|KHR_materials_common (glTF 1.0)} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Vendor/WEB3D_quantized_attributes/README.md|WEB3D_quantized_attributes (glTF 1.0)} * </li><li> * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/AGI_articulations/README.md|AGI_articulations} * </li><li> * {@link https://github.com/KhronosGroup/glTF/pull/1302|KHR_blend (draft)} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_draco_mesh_compression/README.md|KHR_draco_mesh_compression} * </li><li> * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/README.md|KHR_materials_pbrSpecularGlossiness} * </li><li> * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit/README.md|KHR_materials_unlit} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_techniques_webgl/README.md|KHR_techniques_webgl} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_transform/README.md|KHR_texture_transform} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_basisu|KHR_texture_basisu} * </li> * </ul> * </p> * <p> * Note: for models with compressed textures using the KHR_texture_basisu extension, we recommend power of 2 textures in both dimensions * for maximum compatibility. This is because some samplers require power of 2 textures ({@link https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL|Using textures in WebGL}) * and KHR_texture_basisu requires multiple of 4 dimensions ({@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_basisu/README.md#additional-requirements|KHR_texture_basisu additional requirements}). * </p> * <p> * For high-precision rendering, Cesium supports the {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Vendor/CESIUM_RTC/README.md|CESIUM_RTC} extension, which introduces the * CESIUM_RTC_MODELVIEW parameter semantic that says the node is in WGS84 coordinates translated * relative to a local origin. * </p> * * @alias Model * @constructor * * @param {Object} [options] Object with the following properties: * @param {Object|ArrayBuffer|Uint8Array} [options.gltf] A glTF JSON object, or a binary glTF buffer. * @param {Resource|String} [options.basePath=''] The base path that paths in the glTF JSON are relative to. * @param {Boolean} [options.show=true] Determines if the model primitive will be shown. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates. * @param {Number} [options.scale=1.0] A uniform scale applied to this model. * @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom. * @param {Number} [options.maximumScale] The maximum scale size of a model. An upper limit for minimumPixelSize. * @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}. * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded. * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. * @param {Boolean} [options.clampAnimations=true] Determines if the model's animations should hold a pose over frames where no keyframes are specified. * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from light sources. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe. * @param {HeightReference} [options.heightReference=HeightReference.NONE] Determines how the model is drawn relative to terrain. * @param {Scene} [options.scene] Must be passed in for models that use the height reference property. * @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this model will be displayed. * @param {Color} [options.color=Color.WHITE] A color that blends with the model's rendered color. * @param {ColorBlendMode} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] Defines how the color blends with the model. * @param {Number} [options.colorBlendAmount=0.5] Value used to determine the color strength when the <code>colorBlendMode</code> is <code>MIX</code>. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two. * @param {Color} [options.silhouetteColor=Color.RED] The silhouette color. If more than 256 models have silhouettes enabled, there is a small chance that overlapping models will have minor artifacts. * @param {Number} [options.silhouetteSize=0.0] The size of the silhouette in pixels. * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model. * @param {Boolean} [options.dequantizeInShader=true] Determines if a {@link https://github.com/google/draco|Draco} encoded model is dequantized on the GPU. This decreases total memory usage for encoded models. Deprecated in CesiumJS 1.94, will be removed in CesiumJS 1.96. * @param {Cartesian3} [options.lightColor] The light color when shading the model. When <code>undefined</code> the scene's light color is used instead. * @param {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting on this model. * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas. * @param {Boolean} [options.showCreditsOnScreen=false] Whether to display the credits of this model on screen. * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the material's doubleSided property; when false, back face culling is disabled. Back faces are not culled if {@link Model#color} is translucent or {@link Model#silhouetteSize} is greater than 0.0. * @param {Boolean} [options.showOutline=true] Whether to display the outline for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. When true, outlines are displayed. When false, outlines are not displayed. * @param {SplitDirection} [options.splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this model. * * * @see Model.fromGltf * * @demo {@link https://sandcastle.cesium.com/index.html?src=3D%20Models.html|Cesium Sandcastle Models Demo} */ function Model(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const cacheKey = options.cacheKey; this._cacheKey = cacheKey; this._cachedGltf = undefined; this._releaseGltfJson = defaultValue(options.releaseGltfJson, false); let cachedGltf; if ( defined(cacheKey) && defined(gltfCache[cacheKey]) && gltfCache[cacheKey].ready ) { // glTF JSON is in cache and ready cachedGltf = gltfCache[cacheKey]; ++cachedGltf.count; } else { // glTF was explicitly provided, e.g., when a user uses the Model constructor directly let gltf = options.gltf; if (defined(gltf)) { if (gltf instanceof ArrayBuffer) { gltf = new Uint8Array(gltf); } if (gltf instanceof Uint8Array) { // Binary glTF const parsedGltf = parseGlb(gltf); cachedGltf = new CachedGltf({ gltf: parsedGltf, ready: true, }); } else { // Normal glTF (JSON) cachedGltf = new CachedGltf({ gltf: options.gltf, ready: true, }); } cachedGltf.count = 1; if (defined(cacheKey)) { gltfCache[cacheKey] = cachedGltf; } } } setCachedGltf(this, cachedGltf); const basePath = defaultValue(options.basePath, ""); this._resource = Resource.createIfNeeded(basePath); // User specified credit let credit = options.credit; if (typeof credit === "string") { credit = new Credit(credit); } this._credit = credit; // List of credits to be added from the Resource if it is an IonResource this._resourceCredits = []; // List of credits to be added from the glTF this._gltfCredits = []; this._showCreditsOnScreen = defaultValue(options.showCreditsOnScreen, false); /** * Determines if the model primitive will be shown. * * @type {Boolean} * * @default true */ this.show = defaultValue(options.show, true); /** * The silhouette color. * * @type {Color} * * @default Color.RED */ this.silhouetteColor = defaultValue(options.silhouetteColor, Color.RED); this._silhouetteColor = new Color(); this._silhouetteColorPreviousAlpha = 1.0; this._normalAttributeName = undefined; /** * The size of the silhouette in pixels. * * @type {Number} * * @default 0.0 */ this.silhouetteSize = defaultValue(options.silhouetteSize, 0.0); /** * The 4x4 transformation matrix that transforms the model from model to world coordinates. * When this is the identity matrix, the model is drawn in world coordinates, i.e., Earth's WGS84 coordinates. * Local reference frames can be used by providing a different transformation matrix, like that returned * by {@link Transforms.eastNorthUpToFixedFrame}. * * @type {Matrix4} * * @default {@link Matrix4.IDENTITY} * * @example * const origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0); * m.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin); */ this.modelMatrix = Matrix4.clone( defaultValue(options.modelMatrix, Matrix4.IDENTITY) ); this._modelMatrix = Matrix4.clone(this.modelMatrix); this._clampedModelMatrix = undefined; /** * A uniform scale applied to this model before the {@link Model#modelMatrix}. * Values greater than <code>1.0</code> increase the size of the model; values * less than <code>1.0</code> decrease. * * @type {Number} * * @default 1.0 */ this.scale = defaultValue(options.scale, 1.0); this._scale = this.scale; /** * The approximate minimum pixel size of the model regardless of zoom. * This can be used to ensure that a model is visible even when the viewer * zooms out. When <code>0.0</code>, no minimum size is enforced. * * @type {Number} * * @default 0.0 */ this.minimumPixelSize = defaultValue(options.minimumPixelSize, 0.0); this._minimumPixelSize = this.minimumPixelSize; /** * The maximum scale size for a model. This can be used to give * an upper limit to the {@link Model#minimumPixelSize}, ensuring that the model * is never an unreasonable scale. * * @type {Number} */ this.maximumScale = options.maximumScale; this._maximumScale = this.maximumScale; /** * User-defined object returned when the model is picked. * * @type Object * * @default undefined * * @see Scene#pick */ this.id = options.id; this._id = options.id; /** * Returns the height reference of the model * * @type {HeightReference} * * @default HeightReference.NONE */ this.heightReference = defaultValue( options.heightReference, HeightReference.NONE ); this._heightReference = this.heightReference; this._heightChanged = false; this._removeUpdateHeightCallback = undefined; const scene = options.scene; this._scene = scene; if (defined(scene) && defined(scene.terrainProviderChanged)) { this._terrainProviderChangedCallback = scene.terrainProviderChanged.addEventListener( function () { this._heightChanged = true; }, this ); } /** * Used for picking primitives that wrap a model. * * @private */ this._pickObject = options.pickObject; this._allowPicking = defaultValue(options.allowPicking, true); this._ready = false; this._readyPromise = defer(); /** * The currently playing glTF animations. * * @type {ModelAnimationCollection} */ this.activeAnimations = new ModelAnimationCollection(this); /** * Determines if the model's animations should hold a pose over frames where no keyframes are specified. * * @type {Boolean} */ this.clampAnimations = defaultValue(options.clampAnimations, true); this._defaultTexture = undefined; this._incrementallyLoadTextures = defaultValue( options.incrementallyLoadTextures, true ); this._asynchronous = defaultValue(options.asynchronous, true); /** * Determines whether the model casts or receives shadows from light sources. * * @type {ShadowMode} * * @default ShadowMode.ENABLED */ this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED); this._shadows = this.shadows; /** * A color that blends with the model's rendered color. * * @type {Color} * * @default Color.WHITE */ this.color = Color.clone(defaultValue(options.color, Color.WHITE)); this._colorPreviousAlpha = 1.0; /** * Defines how the color blends with the model. * * @type {ColorBlendMode} * * @default ColorBlendMode.HIGHLIGHT */ this.colorBlendMode = defaultValue( options.colorBlendMode, ColorBlendMode.HIGHLIGHT ); /** * Value used to determine the color strength when the <code>colorBlendMode</code> is <code>MIX</code>. * A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with * any value in-between resulting in a mix of the two. * * @type {Number} * * @default 0.5 */ this.colorBlendAmount = defaultValue(options.colorBlendAmount, 0.5); this._colorShadingEnabled = false; this._clippingPlanes = undefined; this.clippingPlanes = options.clippingPlanes; // Used for checking if shaders need to be regenerated due to clipping plane changes. this._clippingPlanesState = 0; // If defined, use this matrix to transform miscellaneous properties like // clipping planes and IBL instead of the modelMatrix. This is so that when // models are part of a tileset these properties get transformed relative to // a common reference (such as the root). this.referenceMatrix = undefined; /** * Whether to cull back-facing geometry. When true, back face culling is * determined by the material's doubleSided property; when false, back face * culling is disabled. Back faces are not culled if {@link Model#color} is * translucent or {@link Model#silhouetteSize} is greater than 0.0. * * @type {Boolean} * * @default true */ this.backFaceCulling = defaultValue(options.backFaceCulling, true); /** * Whether to display the outline for models using the * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. * When true, outlines are displayed. When false, outlines are not displayed. * * @type {Boolean} * @readonly * * @default true */ this.showOutline = defaultValue(options.showOutline, true); /** * The {@link SplitDirection} to apply to this model. * * @type {SplitDirection} * @default {@link SplitDirection.NONE} */ this.splitDirection = defaultValue( options.splitDirection, SplitDirection.NONE ); this._splittingEnabled = false; /** * This property is for debugging only; it is not for production use nor is it optimized. * <p> * Draws the bounding sphere for each draw command in the model. A glTF primitive corresponds * to one draw command. A glTF mesh has an array of primitives, often of length one. * </p> * * @type {Boolean} * * @default false */ this.debugShowBoundingVolume = defaultValue( options.debugShowBoundingVolume, false ); this._debugShowBoundingVolume = false; /** * This property is for debugging only; it is not for production use nor is it optimized. * <p> * Draws the model in wireframe. * </p> * * @type {Boolean} * * @default false */ this.debugWireframe = defaultValue(options.debugWireframe, false); this._debugWireframe = false; this._distanceDisplayCondition = options.distanceDisplayCondition; // Undocumented options this._addBatchIdToGeneratedShaders = options.addBatchIdToGeneratedShaders; this._precreatedAttributes = options.precreatedAttributes; this._vertexShaderLoaded = options.vertexShaderLoaded; this._fragmentShaderLoaded = options.fragmentShaderLoaded; this._uniformMapLoaded = options.uniformMapLoaded; this._pickIdLoaded = options.pickIdLoaded; this._ignoreCommands = defaultValue(options.ignoreCommands, false); this._requestType = options.requestType; this._upAxis = defaultValue(options.upAxis, Axis.Y); this._gltfForwardAxis = Axis.Z; this._forwardAxis = options.forwardAxis; /** * @private * @readonly */ this.cull = defaultValue(options.cull, true); /** * @private * @readonly */ this.opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE); this._computedModelMatrix = new Matrix4(); // Derived from modelMatrix and scale this._clippingPlanesMatrix = Matrix4.clone(Matrix4.IDENTITY); // Derived from reference matrix and the current view matrix this._iblReferenceFrameMatrix = Matrix3.clone(Matrix3.IDENTITY); // Derived from reference matrix and the current view matrix this._initialRadius = undefined; // Radius without model's scale property, model-matrix scale, animations, or skins this._boundingSphere = undefined; this._scaledBoundingSphere = new BoundingSphere(); this._state = ModelState.NEEDS_LOAD; this._loadResources = undefined; this._mode = undefined; this._perNodeShowDirty = false; // true when the Cesium API was used to change a node's show property this._cesiumAnimationsDirty = false; // true when the Cesium API, not a glTF animation, changed a node transform this._dirty = false; // true when the model was transformed this frame this._maxDirtyNumber = 0; // Used in place of a dirty boolean flag to avoid an extra graph traversal this._runtime = { animations: undefined, articulationsByName: undefined, articulationsByStageKey: undefined, stagesByKey: undefined, rootNodes: undefined, nodes: undefined, // Indexed with the node's index nodesByName: undefined, // Indexed with name property in the node skinnedNodes: undefined, meshesByName: undefined, // Indexed with the name property in the mesh materialsByName: undefined, // Indexed with the name property in the material materialsById: undefined, // Indexed with the material's index }; this._uniformMaps = {}; // Not cached since it can be targeted by glTF animation this._extensionsUsed = undefined; // Cached used glTF extensions this._extensionsRequired = undefined; // Cached required glTF extensions this._quantizedUniforms = {}; // Quantized uniforms for each program for WEB3D_quantized_attributes this._programPrimitives = {}; this._rendererResources = { // Cached between models with the same url/cache-key buffers: {}, vertexArrays: {}, programs: {}, sourceShaders: {}, silhouettePrograms: {}, textures: {}, samplers: {}, renderStates: {}, }; this._cachedRendererResources = undefined; this._loadRendererResourcesFromCache = false; if (options.dequantizeInShader) { deprecationWarning( "Model.dequantizeInShader", "The Model dequantizeInShader constructor parameter was deprecated in CesiumJS 1.94 and will be removed in 1.96" ); } this._dequantizeInShader = defaultValue(options.dequantizeInShader, true); this._decodedData = {}; this._cachedGeometryByteLength = 0; this._cachedTexturesByteLength = 0; this._geometryByteLength = 0; this._texturesByteLength = 0; this._trianglesLength = 0; this._pointsLength = 0; // Hold references for shader reconstruction. // Hold these separately because _cachedGltf may get released (this.releaseGltfJson) this._sourceTechniques = {}; this._sourcePrograms = {}; this._quantizedVertexShaders = {}; this._nodeCommands = []; this._pickIds = []; // CESIUM_RTC extension this._rtcCenter = undefined; // reference to either 3D or 2D this._rtcCenterEye = undefined; // in eye coordinates this._rtcCenter3D = undefined; // in world coordinates this._rtcCenter2D = undefined; // in projected world coordinates this._sourceVersion = undefined; this._sourceKHRTechniquesWebGL = undefined; this._lightColor = Cartesian3.clone(options.lightColor); if (defined(options.imageBasedLighting)) { this._imageBasedLighting = options.imageBasedLighting; this._shouldDestroyImageBasedLighting = false; } else { this._imageBasedLighting = new ImageBasedLighting(); this._shouldDestroyImageBasedLighting = true; } this._shouldRegenerateShaders = false; } Object.defineProperties(Model.prototype, { /** * The object for the glTF JSON, including properties with default values omitted * from the JSON provided to this model. * * @deprecated * * @memberof Model.prototype * * @type {Object} * @readonly * * @default undefined */ gltf: { get: function () { deprecationWarning( "Model.gltf", "Model.gltf getter was deprecated in CesiumJS 1.94 and will be removed in 1.96" ); return this.gltfInternal; }, }, /** * See https://github.com/CesiumGS/cesium/pull/10415#issuecomment-1143600984 * @private */ gltfInternal: { get: function () { return defined(this._cachedGltf) ? this._cachedGltf.gltf : undefined; }, }, /** * When <code>true</code>, the glTF JSON is not stored with the model once the model is * loaded (when {@link Model#ready} is <code>true</code>). This saves memory when * geometry, textures, and animations are embedded in the .gltf file. * This is especially useful for cases like 3D buildings, where each .gltf model is unique * and caching the glTF JSON is not effective. * * @memberof Model.prototype * * @type {Boolean} * @readonly * * @default false * * @private */ releaseGltfJson: { get: function () { return this._releaseGltfJson; }, }, /** * The key identifying this model in the model cache for glTF JSON, renderer resources, and animations. * Caching saves memory and improves loading speed when several models with the same url are created. * <p> * This key is automatically generated when the model is created with {@link Model.fromGltf}. If the model * is created directly from glTF JSON using the {@link Model} constructor, this key can be manually * provided; otherwise, the model will not be changed. * </p> * * @memberof Model.prototype * * @type {String} * @readonly * * @private */ cacheKey: { get: function () { return this._cacheKey; }, }, /** * The base path that paths in the glTF JSON are relative to. The base * path is the same path as the path containing the .gltf file * minus the .gltf file, when binary, image, and shader files are * in the same directory as the .gltf. When this is <code>''</code>, * the app's base path is used. * * @deprecated * * @memberof Model.prototype * * @type {String} * @readonly * * @default '' */ basePath: { get: function () { deprecationWarning( "model.basePath", "Model.basePath getter is deprecated in CesiumJS 1.94. It will be removed in CesiumJS 1.96" ); return this.basePathInternal; }, }, /** * See https://github.com/CesiumGS/cesium/pull/10415#issuecomment-1143600984 * @private */ basePathInternal: { get: function () { return this._resource.url; }, }, /** * The model's bounding sphere in its local coordinate system. This does not take into * account glTF animations and skins nor does it take into account {@link Model#minimumPixelSize}. * * @memberof Model.prototype * * @type {BoundingSphere} * @readonly * * @default undefined * * @exception {DeveloperError} The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true. * * @example * // Center in WGS84 coordinates * const center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3()); */ boundingSphere: { get: function () { deprecationWarning( "model.boundingSphere", "Model.boundingSphere currently returns results in model space. In CesiumJS 1.96, model.boundingSphere will be changed to return results in world space. The calling code will no longer need to multiply the bounding sphere by the model matrix" ); return this.boundingSphereInternal; }, }, /* * See https://github.com/CesiumGS/cesium/pull/10415#issuecomment-1143600984 * @private */ boundingSphereInternal: { get: function () { //>>includeStart('debug', pragmas.debug); if (this._state !== ModelState.LOADED) { throw new DeveloperError( "The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true." ); } //>>includeEnd('debug'); let modelMatrix = this.modelMatrix; if ( this.heightReference !== HeightReference.NONE && this._clampedModelMatrix ) { modelMatrix = this._clampedModelMatrix; } const nonUniformScale = Matrix4.getScale( modelMatrix, boundingSphereCartesian3Scratch ); const scale = defined(this.maximumScale) ? Math.min(this.maximumScale, this.scale) : this.scale; Cartesian3.multiplyByScalar(nonUniformScale, scale, nonUniformScale); const scaledBoundingSphere = this._scaledBoundingSphere; scaledBoundingSphere.center = Cartesian3.multiplyComponents( this._boundingSphere.center, nonUniformScale, scaledBoundingSphere.center ); scaledBoundingSphere.radius = Cartesian3.maximumComponent(nonUniformScale) * this._initialRadius; if (defined(this._rtcCenter)) { Cartesian3.add( this._rtcCenter, scaledBoundingSphere.center, scaledBoundingSphere.center ); } return scaledBoundingSphere; }, }, /** * When <code>true</code>, this model is ready to render, i.e., the external binary, image, * and shader files were downloaded and the WebGL resources were created. This is set to * <code>true</code> right before {@link Model#readyPromise} is resolved. * * @memberof Model.prototype * * @type {Boolean} * @readonly * * @default false */ ready: { get: function () { return this._ready; }, }, /** * Gets the promise that will be resolved when this model is ready to render, i.e., when the external binary, image, * and shader files were downloaded and the WebGL resources were created. * <p> * This promise is resolved at the end of the frame before the first frame the model is rendered in. * </p> * * @memberof Model.prototype * @type {Promise.<Model>} * @readonly * * @example * // Play all animations at half-speed when the model is ready to render * Promise.resolve(model.readyPromise).then(function(model) { * model.activeAnimations.addAll({ * multiplier : 0.5 * }); * }).catch(function(error){ * window.alert(error); * }); * * @see Model#ready */ readyPromise: { get: function () { return this._readyPromise.promise; }, }, /** * Determines if model WebGL resource creation will be spread out over several frames or * block until completion once all glTF files are loaded. * * @memberof Model.prototype * * @type {Boolean} * @readonly * * @default true */ asynchronous: { get: function () { return this._asynchronous; }, }, /** * When <code>true</code>, each glTF mesh and primitive is pickable with {@link Scene#pick}. When <code>false</code>, GPU memory is saved. * * @memberof Model.prototype * * @type {Boolean} * @readonly * * @default true */ allowPicking: { get: function () { return this._allowPicking; }, }, /** * Determine if textures may continue to stream in after the model is loaded. * * @memberof Model.prototype * * @type {Boolean} * @readonly * * @default true */ incrementallyLoadTextures: { get: function () { return this._incrementallyLoadTextures; }, }, /** * Return the number of pending texture loads. * * @deprecated * * @memberof Model.prototype * * @type {Number} * @readonly */ pendingTextureLoads: { get: function () { deprecationWarning( "Model.pendingTextureLoads", "The Model.pendingTextureLoads getter was deprecated in CesiumJS 1.94 and will be removed in CesiumJS 1.96" ); return this.pendingTextureLoadsInternal; }, }, /** * See https://github.com/CesiumGS/cesium/pull/10415#issuecomment-1143600984 * @private */ pendingTextureLoadsInternal: { get: function () { return defined(this._loadResources) ? this._loadResources.pendingTextureLoads : 0; }, }, /** * Returns true if the model was transformed this frame * * @memberof Model.prototype * * @type {Boolean} * @readonly * * @private */ dirty: { get: function () { return this._dirty; }, }, /** * Gets or sets the condition specifying at what distance from the camera that this model will be displayed. * @memberof Model.prototype * @type {DistanceDisplayCondition} * @default undefined */ distanceDisplayCondition: { get: function () { return this._distanceDisplayCondition; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (defined(value) && value.far <= value.near) { throw new DeveloperError("far must be greater than near"); } //>>includeEnd('debug'); this._distanceDisplayCondition = DistanceDisplayCondition.clone( value, this._distanceDisplayCondition ); }, }, extensionsUsed: { get: function () { if (!defined(this._extensionsUsed)) { this._extensionsUsed = ModelUtility.getUsedExtensions( this.gltfInternal ); } return this._extensionsUsed; }, }, extensionsRequired: { get: function () { if (!defined(this._extensionsRequired)) { this._extensionsRequired = ModelUtility.getRequiredExtensions( this.gltfInternal ); } return this._extensionsRequired; }, }, /** * Gets the model's up-axis. * By default models are y-up according to the glTF spec, however geo-referenced models will typically be z-up. * * @memberof Model.prototype * * @type {Number} * @default Axis.Y * @readonly * * @private */ upAxis: { get: function () { return this._upAxis; }, }, /** * Gets the model's forward axis. * By default, glTF 2.0 models are z-forward according to the glTF spec, however older * glTF (1.0, 0.8) models used x-forward. Note that only Axis.X and Axis.Z are supported. * * @memberof Model.prototype * * @type {Number} * @default Axis.Z * @readonly * * @private */ forwardAxis: { get: function () { if (defined(this._forwardAxis)) { return this._forwardAxis; } return this._gltfForwardAxis; }, }, /** * Gets the model's triangle count. * * @private */ trianglesLength: { get: function () { return this._trianglesLength; }, }, /** * Gets the model's point count. * * @private */ pointsLength: { get: function () { return this._pointsLength; }, }, /** * Gets the model's geometry memory in bytes. This includes all vertex and index buffers. * * @private */ geometryByteLength: { get: function () { return this._geometryByteLength; }, }, /** * Gets the model's texture memory in bytes. * * @private */ texturesByteLength: { get: function () { return this._texturesByteLength; }, }, /** * Gets the model's cached geometry memory in bytes. This includes all vertex and index buffers. * * @private */ cachedGeometryByteLength: { get: function () { return this._cachedGeometryByteLength; }, }, /** * Gets the model's cached texture memory in bytes. * * @private */ cachedTexturesByteLength: { get: function () { return this._cachedTexturesByteLength; }, }, /** * The {@link ClippingPlaneCollection} used to selectively disable rendering the model. * * @memberof Model.prototype * * @type {ClippingPlaneCollection} */ clippingPlanes: { get: function () { return this._clippingPlanes; }, set: function (value) { if (value === this._clippingPlanes) { return; } // Handle destroying, checking of unknown, checking for existing ownership ClippingPlaneCollection.setOwner(value, this, "_clippingPlanes"); }, }, /** * @private */ pickIds: { get: function () { return this._pickIds; }, }, /** * The light color when shading the model. When <code>undefined</code> the scene's light color is used instead. * <p> * For example, disabling additional light sources by setting * <code>model.imageBasedLighting.imageBasedLightingFactor = new Cesium.Cartesian2(0.0, 0.0)</code> * will make the model much darker. Here, increasing the intensity of the light source will make the model brighter. * </p> * * @memberof Model.prototype * * @type {Cartesian3} * @default undefined */ lightColor: { get: function () { return this._lightColor; }, set: function (value) { const lightColor = this._lightColor; if (value === lightColor || Cartesian3.equals(value, lightColor)) { return; } this._shouldRegenerateShaders = this._shouldRegenerateShaders || (defined(lightColor) && !defined(value)) || (defined(value) && !defined(lightColor)); this._lightColor = Cartesian3.clone(value, lightColor); }, }, /** * The properties for managing image-based lighting on this model. * * @memberof Model.prototype * * @type {ImageBasedLighting} */ imageBasedLighting: { get: function () { return this._imageBasedLighting; }, set: function (value) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("imageBasedLighting", this._imageBasedLighting); //>>includeEnd('debug'); if (value !== this._imageBasedLighting) { if ( this._shouldDestroyImageBasedLighting && !this._imageBasedLighting.isDestroyed() ) { this._imageBasedLighting.destroy(); } this._imageBasedLighting = value; this._shouldDestroyImageBasedLighting = false; this._shouldRegenerateShaders = true; } }, }, /** * Gets the credit that will be displayed for the model * @memberof Model.prototype * @type {Credit} */ credit: { get: function () { return this._credit; }, }, /** * Gets or sets whether the credits of the model will be displayed on the screen * @memberof Model.prototype * @type {Boolean} */ showCreditsOnScreen: { get: function () { return this._showCreditsOnScreen; }, set: function (value) { if (this._showCreditsOnScreen !== value) { if (defined(this._credit)) { this._credit.showOnScreen = value; } const resourceCreditsLength = this._resourceCredits.length; for (let i = 0; i < resourceCreditsLength; i++) { this._resourceCredits[i].showOnScreen = value; } const gltfCreditsLength = this._gltfCredits.length; for (let i = 0; i < gltfCreditsLength; i++) { this._gltfCredits[i].showOnScreen = value; } } this._showCreditsOnScreen = value; }, }, }); function silhouetteSupported(context) { return context.stencilBuffer; } function isColorShadingEnabled(model) { return ( !Color.equals(model.color, Color.WHITE) || model.colorBlendMode !== ColorBlendMode.HIGHLIGHT ); } function isClippingEnabled(model) { const clippingPlanes = model._clippingPlanes; return ( defined(clippingPlanes) && clippingPlanes.enabled && clippingPlanes.length !== 0 ); } /** * Determines if silhouettes are supported. * * @param {Scene} scene The scene. * @returns {Boolean} <code>true</code> if silhouettes are supported; otherwise, returns <code>false</code> */ Model.silhouetteSupported = function (scene) { return silhouetteSupported(scene.context); }; function containsGltfMagic(uint8Array) { const magic = getMagic(uint8Array); return magic === "glTF"; } /** * <p> * Creates a model from a glTF asset. When the model is ready to render, i.e., when the external binary, image, * and shader files are downloaded and the WebGL resources are created, the {@link Model#readyPromise} is resolved. * </p> * <p> * The model can be a traditional glTF asset with a .gltf extension or a Binary glTF using the .glb extension. * </p> * <p> * Cesium supports glTF assets with the following extensions: * <ul> * <li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Khronos/KHR_binary_glTF/README.md|KHR_binary_glTF (glTF 1.0)} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Khronos/KHR_materials_common/README.md|KHR_materials_common (glTF 1.0)} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Vendor/WEB3D_quantized_attributes/README.md|WEB3D_quantized_attributes (glTF 1.0)} * </li><li> * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/AGI_articulations/README.md|AGI_articulations} * </li><li> * {@link https://github.com/KhronosGroup/glTF/pull/1302|KHR_blend (draft)} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_draco_mesh_compression/README.md|KHR_draco_mesh_compression} * </li><li> * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/README.md|KHR_materials_pbrSpecularGlossiness} * </li><li> * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit/README.md|KHR_materials_unlit} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_techniques_webgl/README.md|KHR_techniques_webgl} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_transform/README.md|KHR_texture_transform} * </li><li> * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_basisu/README.md|KHR_texture_basisu} * </li> * </ul> * </p> * <p> * For high-precision rendering, Cesium supports the {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Vendor/CESIUM_RTC/README.md|CESIUM_RTC} extension, which introduces the * CESIUM_RTC_MODELVIEW parameter semantic that says the node is in WGS84 coordinates translated * relative to a local origin. * </p> * * @param {Object} options Object with the following properties: * @param {Resource|String} options.url The url to the .gltf file. * @param {Resource|String} [options.basePath] The base path that paths in the glTF JSON are relative to. * @param {Boolean} [options.show=true] Determines if the model primitive will be shown. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates. * @param {Number} [options.scale=1.0] A uniform scale applied to this model. * @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom. * @param {Number} [options.maximumScale] The maximum scale for the model. * @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}. * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded. * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. * @param {Boolean} [options.clampAnimations=true] Determines if the model's animations should hold a pose over frames where no keyframes are specified. * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from light sources. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe. * @param {HeightReference} [options.heightReference=HeightReference.NONE] Determines how the model is drawn relative to terrain. * @param {Scene} [options.scene] Must be passed in for models that use the height reference property. * @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this model will be displayed. * @param {Color} [options.color=Color.WHITE] A color that blends with the model's rendered color. * @param {ColorBlendMode} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] Defines how the color blends