/*global define*/ define([ '../Core/BoundingSphere', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/clone', '../Core/combine', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', '../Core/Event', '../Core/IndexDatatype', '../Core/loadArrayBuffer', '../Core/loadImage', '../Core/loadText', '../Core/Math', '../Core/Matrix2', '../Core/Matrix3', '../Core/Matrix4', '../Core/PrimitiveType', '../Core/Quaternion', '../Core/Queue', '../Core/RuntimeError', '../Renderer/BufferUsage', '../Renderer/DrawCommand', '../Renderer/ShaderSource', '../Renderer/TextureMinificationFilter', '../Renderer/TextureWrap', '../ThirdParty/gltfDefaults', '../ThirdParty/Uri', './getModelAccessor', './ModelAnimationCache', './ModelAnimationCollection', './ModelMaterial', './ModelMesh', './ModelNode', './Pass', './SceneMode' ], function( BoundingSphere, Cartesian2, Cartesian3, Cartesian4, clone, combine, defaultValue, defined, defineProperties, destroyObject, DeveloperError, Event, IndexDatatype, loadArrayBuffer, loadImage, loadText, CesiumMath, Matrix2, Matrix3, Matrix4, PrimitiveType, Quaternion, Queue, RuntimeError, BufferUsage, DrawCommand, ShaderSource, TextureMinificationFilter, TextureWrap, gltfDefaults, Uri, getModelAccessor, ModelAnimationCache, ModelAnimationCollection, ModelMaterial, ModelMesh, ModelNode, Pass, SceneMode) { "use strict"; /*global WebGLRenderingContext*/ var yUpToZUp = Matrix4.fromRotationTranslation(Matrix3.fromRotationX(CesiumMath.PI_OVER_TWO)); var ModelState = { NEEDS_LOAD : 0, LOADING : 1, LOADED : 2, FAILED : 3 }; function LoadResources() { this.buffersToCreate = new Queue(); this.buffers = {}; this.pendingBufferLoads = 0; this.programsToCreate = new Queue(); this.shaders = {}; this.pendingShaderLoads = 0; this.texturesToCreate = new Queue(); this.pendingTextureLoads = 0; this.createSamplers = true; this.createSkins = true; this.createRuntimeAnimations = true; this.createVertexArrays = true; this.createRenderStates = true; this.createUniformMaps = true; this.createRuntimeNodes = true; this.skinnedNodesNames = []; } LoadResources.prototype.finishedPendingLoads = function() { return ((this.pendingBufferLoads === 0) && (this.pendingShaderLoads === 0) && (this.pendingTextureLoads === 0)); }; LoadResources.prototype.finishedResourceCreation = function() { return ((this.buffersToCreate.length === 0) && (this.programsToCreate.length === 0) && (this.texturesToCreate.length === 0)); }; LoadResources.prototype.finishedBuffersCreation = function() { return ((this.pendingBufferLoads === 0) && (this.buffersToCreate.length === 0)); }; LoadResources.prototype.finishedProgramCreation = function() { return ((this.pendingShaderLoads === 0) && (this.programsToCreate.length === 0)); }; LoadResources.prototype.finishedTextureCreation = function() { return ((this.pendingTextureLoads === 0) && (this.texturesToCreate.length === 0)); }; /////////////////////////////////////////////////////////////////////////// // 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. var CachedGltf = function(options) { this._gltf = gltfDefaults(options.gltf); this.ready = options.ready; this.modelsToLoad = []; this.count = 0; }; defineProperties(CachedGltf.prototype, { gltf : { set : function(value) { this._gltf = gltfDefaults(value); }, get : function() { return this._gltf; } } }); var gltfCache = {}; function getAnimationIds(cachedGltf) { var animationIds = []; if (defined(cachedGltf) && defined(cachedGltf.gltf)) { var animations = cachedGltf.gltf.animations; for (var name in animations) { if (animations.hasOwnProperty(name)) { animationIds.push(name); } } } return animationIds; } /////////////////////////////////////////////////////////////////////////// /** * A 3D model based on glTF, the runtime asset format for WebGL, OpenGL ES, and OpenGL. *

* 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. *

*

* 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#readyToRender} event is fired 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. *

* * @alias Model * @constructor * * @param {Object} [options] Object with the following properties: * @param {Object} [options.gltf] The object for the glTF JSON. * @param {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 {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}. * @param {Boolean} [options.allowPicking=true] When true, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @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.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. * * @see Model.fromGltf * @see Model#readyToRender * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle Models Demo} */ var Model = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var cacheKey = options.cacheKey; this._cacheKey = cacheKey; this._cachedGltf = undefined; this._releaseGltfJson = defaultValue(options.releaseGltfJson, false); this._animationIds = undefined; var 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 if (defined(options.gltf)) { cachedGltf = new CachedGltf({ gltf : options.gltf, ready : true }); cachedGltf.count = 1; if (defined(cacheKey)) { gltfCache[cacheKey] = cachedGltf; } } } setCachedGltf(this, cachedGltf); this._basePath = defaultValue(options.basePath, ''); var docUri = new Uri(document.location.href); var modelUri = new Uri(this._basePath); this._baseUri = modelUri.resolve(docUri); /** * Determines if the model primitive will be shown. * * @type {Boolean} * * @default true */ this.show = defaultValue(options.show, true); /** * 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 * var 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); /** * A uniform scale applied to this model before the {@link Model#modelMatrix}. * Values greater than 1.0 increase the size of the model; values * less than 1.0 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 0.0, no minimum size is enforced. * * @type {Number} * * @default 0.0 */ this.minimumPixelSize = defaultValue(options.minimumPixelSize, 0.0); this._minimumPixelSize = this.minimumPixelSize; /** * User-defined object returned when the model is picked. * * @type Object * * @default undefined * * @see Scene#pick */ this.id = options.id; this._id = options.id; /** * Used for picking primitives that wrap a model. * * @private */ this.pickPrimitive = options.pickPrimitive; this._allowPicking = defaultValue(options.allowPicking, true); /** * The event fired 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. *

* This event is fired at the end of the frame before the first frame the model is rendered in. *

* * @type {Event} * @default new Event() * * @example * // Play all animations at half-speed when the model is ready to render * model.readyToRender.addEventListener(function(model) { * model.activeAnimations.addAll({ * speedup : 0.5 * }); * }); * * @see Model#ready */ this.readyToRender = new Event(); this._ready = false; /** * The currently playing glTF animations. * * @type {ModelAnimationCollection} */ this.activeAnimations = new ModelAnimationCollection(this); this._asynchronous = defaultValue(options.asynchronous, true); /** * This property is for debugging only; it is not for production use nor is it optimized. *

* 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. *

* * @type {Boolean} * * @default false */ this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); this._debugShowBoudingVolume = false; /** * This property is for debugging only; it is not for production use nor is it optimized. *

* Draws the model in wireframe. *

* * @type {Boolean} * * @default false */ this.debugWireframe = defaultValue(options.debugWireframe, false); this._debugWireframe = false; this._computedModelMatrix = new Matrix4(); // Derived from modelMatrix and scale this._initialRadius = undefined; // Radius without model's scale property, model-matrix scale, animations, or skins this._boundingSphere = undefined; this._state = ModelState.NEEDS_LOAD; this._loadError = undefined; this._loadResources = 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._maxDirtyNumber = 0; // Used in place of a dirty boolean flag to avoid an extra graph traversal this._runtime = { animations : undefined, rootNodes : undefined, nodes : undefined, // Indexed with the node property's name, i.e., glTF id 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 property name }; this._uniformMaps = {}; // Not cached since it can be targeted by glTF animation this._rendererResources = { // Cached between models with the same url/cache-key buffers : {}, vertexArrays : {}, programs : {}, pickPrograms : {}, textures : {}, samplers : {}, renderStates : {} }; this._cachedRendererResources = undefined; this._loadRendererResourcesFromCache = false; this._nodeCommands = []; this._pickIds = []; }; function setCachedGltf(model, cachedGltf) { model._cachedGltf = cachedGltf; model._animationIds = getAnimationIds(cachedGltf); } defineProperties(Model.prototype, { /** * The object for the glTF JSON, including properties with default values omitted * from the JSON provided to this model. * * @memberof Model.prototype * * @type {Object} * @readonly * * @default undefined */ gltf : { get : function() { return defined(this._cachedGltf) ? this._cachedGltf.gltf : undefined; } }, /** * When true, the glTF JSON is not stored with the model once the model is * loaded (when {@link Model#ready} is true). This saves memory when * geometry, textures, and animations are embedded in the .gltf file, which is the * default for the {@link http://cesiumjs.org/convertmodel.html|Cesium model converter}. * 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. *

* 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. *

* * @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 .json file * minus the .json file, when binary, image, and shader files are * in the same directory as the .json. When this is '', * the app's base path is used. * * @memberof Model.prototype * * @type {String} * @readonly * * @default '' */ basePath : { get : function() { return this._basePath; } }, /** * The model's bounding sphere in its local coordinate system. This does not take into * account glTF animation and skins or {@link Model#scale}. * * @memberof Model.prototype * * @type {BoundingSphere} * @readonly * * @default undefined * * @exception {DeveloperError} The model is not loaded. Wait for the model's readyToRender event or ready property. * * @example * // Center in WGS84 coordinates * var center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3()); */ boundingSphere : { get : function() { //>>includeStart('debug', pragmas.debug); if (this._state !== ModelState.LOADED) { throw new DeveloperError('The model is not loaded. Wait for the model\'s readyToRender event or ready property.'); } //>>includeEnd('debug'); this._boundingSphere.radius = (this.scale * Matrix4.getMaximumScale(this.modelMatrix)) * this._initialRadius; return this._boundingSphere; } }, /** * When true, 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 * true right before {@link Model#readyToRender} is fired. * * @memberof Model.prototype * * @type {Boolean} * @readonly * * @default false * * @see Model#readyToRender */ ready : { get : function() { return this._ready; } }, /** * 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 true, each glTF mesh and primitive is pickable with {@link Scene#pick}. When false, GPU memory is saved. * * @memberof Model.prototype * * @type {Boolean} * @readonly * * @default true */ allowPicking : { get : function() { return this._allowPicking; } } }); /** * 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#readyToRender} event is fired. * * @param {Object} options Object with the following properties: * @param {String} options.url The url to the .gltf file. * @param {Object} [options.headers] HTTP headers to send with the request. * @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 {Boolean} [options.allowPicking=true] When true, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @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.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each {@link DrawCommand} in the model. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe. * @returns {Model} The newly created model. * * @see Model#readyToRender * * @example * // Example 1. Create a model from a glTF asset * var model = scene.primitives.add(Cesium.Model.fromGltf({ * url : './duck/duck.json' * })); * * @example * // Example 2. Create model and provide all properties and events * var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0); * var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin); * * var model = scene.primitives.add(Model.fromGltf({ * url : './duck/duck.json', * show : true, // default * modelMatrix : modelMatrix, * scale : 2.0, // double size * minimumPixelSize : 128, // never smaller than 128 pixels * allowPicking : false, // not pickable * debugShowBoundingVolume : false, // default * debugWireframe : false * })); * * model.readyToRender.addEventListener(function(model) { * // Play all animations when the model is ready to render * model.activeAnimations.addAll(); * }); */ Model.fromGltf = function(options) { //>>includeStart('debug', pragmas.debug); if (!defined(options) || !defined(options.url)) { throw new DeveloperError('options.url is required'); } //>>includeEnd('debug'); var url = options.url; var basePath = ''; var i = url.lastIndexOf('/'); if (i !== -1) { basePath = url.substring(0, i + 1); } var cacheKey = options.cacheKey; if (!defined(cacheKey)) { // Use absolute URL, since two URLs with different relative paths could point to the same model. var docUri = new Uri(document.location.href); var modelUri = new Uri(url); cacheKey = modelUri.resolve(docUri).toString(); } options = clone(options); options.basePath = basePath; options.cacheKey = cacheKey; var model = new Model(options); var cachedGltf = gltfCache[cacheKey]; if (!defined(cachedGltf)) { cachedGltf = new CachedGltf({ ready : false }); cachedGltf.count = 1; cachedGltf.modelsToLoad.push(model); setCachedGltf(model, cachedGltf); gltfCache[cacheKey] = cachedGltf; loadText(url, options.headers).then(function(data) { cachedGltf.gltf = JSON.parse(data); var models = cachedGltf.modelsToLoad; var length = models.length; for (var i = 0; i < length; ++i) { var m = models[i]; if (!m.isDestroyed()) { setCachedGltf(m, cachedGltf); } } cachedGltf.modelsToLoad = undefined; cachedGltf.ready = true; }).otherwise(getFailedLoadFunction(model, 'gltf', url)); } else if (!cachedGltf.ready) { // Cache hit but the loadText() request is still pending ++cachedGltf.count; cachedGltf.modelsToLoad.push(model); } // else if the cached glTF is defined and ready, the // model constructor will pick it up using the cache key. return model; }; /** * For the unit tests to verify model caching. * * @private */ Model._gltfCache = gltfCache; function getRuntime(model, runtimeName, name) { //>>includeStart('debug', pragmas.debug); if (model._state !== ModelState.LOADED) { throw new DeveloperError('The model is not loaded. Wait for the model\'s readyToRender event or ready property.'); } if (!defined(name)) { throw new DeveloperError('name is required.'); } //>>includeEnd('debug'); return (model._runtime[runtimeName])[name]; } /** * Returns the glTF node with the given name property. This is used to * modify a node's transform for animation outside of glTF animations. * * @param {String} name The glTF name of the node. * @returns {ModelNode} The node or undefined if no node with name exists. * * @exception {DeveloperError} The model is not loaded. Wait for the model's readyToRender event or ready property. * * @example * // Apply non-uniform scale to node LOD3sp * var node = model.getNode('LOD3sp'); * node.matrix =Cesium. Matrix4.fromScale(new Cesium.Cartesian3(5.0, 1.0, 1.0), node.matrix); */ Model.prototype.getNode = function(name) { var node = getRuntime(this, 'nodesByName', name); return defined(node) ? node.publicNode : undefined; }; /** * Returns the glTF mesh with the given name property. * * @param {String} name The glTF name of the mesh. * * @returns {ModelMesh} The mesh or undefined if no mesh with name exists. * * @exception {DeveloperError} The model is not loaded. Wait for the model's readyToRender event or ready property. */ Model.prototype.getMesh = function(name) { return getRuntime(this, 'meshesByName', name); }; /** * Returns the glTF material with the given name property. * * @param {String} name The glTF name of the material. * @returns {ModelMaterial} The material or undefined if no material with name exists. * * @exception {DeveloperError} The model is not loaded. Wait for the model's readyToRender event or ready property. */ Model.prototype.getMaterial = function(name) { return getRuntime(this, 'materialsByName', name); }; var aMinScratch = new Cartesian3(); var aMaxScratch = new Cartesian3(); function computeBoundingSphere(gltf) { var gltfNodes = gltf.nodes; var gltfMeshes = gltf.meshes; var gltfAccessors = gltf.accessors; var rootNodes = gltf.scenes[gltf.scene].nodes; var rootNodesLength = rootNodes.length; var nodeStack = []; var min = new Cartesian3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); var max = new Cartesian3(Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE); for (var i = 0; i < rootNodesLength; ++i) { var n = gltfNodes[rootNodes[i]]; n._transformToRoot = getTransform(n); nodeStack.push(n); while (nodeStack.length > 0) { n = nodeStack.pop(); var transformToRoot = n._transformToRoot; var meshes = defaultValue(n.meshes, defined(n.instanceSkin) ? n.instanceSkin.meshes : undefined); if (defined(meshes)) { var meshesLength = meshes.length; for (var j = 0; j < meshesLength; ++j) { var primitives = gltfMeshes[meshes[j]].primitives; var primitivesLength = primitives.length; for (var m = 0; m < primitivesLength; ++m) { var position = primitives[m].attributes.POSITION; if (defined(position)) { var accessor = gltfAccessors[position]; var aMin = Cartesian3.fromArray(accessor.min, 0, aMinScratch); var aMax = Cartesian3.fromArray(accessor.max, 0, aMaxScratch); if (defined(min) && defined(max)) { Matrix4.multiplyByPoint(transformToRoot, aMin, aMin); Matrix4.multiplyByPoint(transformToRoot, aMax, aMax); Cartesian3.minimumByComponent(min, aMin, min); Cartesian3.maximumByComponent(max, aMax, max); } } } } } var children = n.children; var childrenLength = children.length; for (var k = 0; k < childrenLength; ++k) { var child = gltfNodes[children[k]]; child._transformToRoot = getTransform(child); Matrix4.multiplyTransformation(transformToRoot, child._transformToRoot, child._transformToRoot); nodeStack.push(child); } delete n._transformToRoot; } } var boundingSphere = BoundingSphere.fromCornerPoints(min, max); return BoundingSphere.transformWithoutScale(boundingSphere, yUpToZUp, boundingSphere); } /////////////////////////////////////////////////////////////////////////// function getFailedLoadFunction(model, type, path) { return function() { model._loadError = new RuntimeError('Failed to load external ' + type + ': ' + path); model._state = ModelState.FAILED; }; } function bufferLoad(model, name) { return function(arrayBuffer) { var loadResources = model._loadResources; loadResources.buffers[name] = arrayBuffer; --loadResources.pendingBufferLoads; }; } function parseBuffers(model) { var buffers = model.gltf.buffers; for (var name in buffers) { if (buffers.hasOwnProperty(name)) { ++model._loadResources.pendingBufferLoads; var buffer = buffers[name]; var uri = new Uri(buffer.uri); var bufferPath = uri.resolve(model._baseUri).toString(); loadArrayBuffer(bufferPath).then(bufferLoad(model, name)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath)); } } } function parseBufferViews(model) { var bufferViews = model.gltf.bufferViews; for (var name in bufferViews) { if (bufferViews.hasOwnProperty(name)) { model._loadResources.buffersToCreate.enqueue(name); } } } function shaderLoad(model, name) { return function(source) { var loadResources = model._loadResources; loadResources.shaders[name] = source; --loadResources.pendingShaderLoads; }; } function parseShaders(model) { var shaders = model.gltf.shaders; for (var name in shaders) { if (shaders.hasOwnProperty(name)) { ++model._loadResources.pendingShaderLoads; var shader = shaders[name]; var uri = new Uri(shader.uri); var shaderPath = uri.resolve(model._baseUri).toString(); loadText(shaderPath).then(shaderLoad(model, name)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath)); } } } function parsePrograms(model) { var programs = model.gltf.programs; for (var name in programs) { if (programs.hasOwnProperty(name)) { model._loadResources.programsToCreate.enqueue(name); } } } function imageLoad(model, name) { return function(image) { var loadResources = model._loadResources; --loadResources.pendingTextureLoads; loadResources.texturesToCreate.enqueue({ name : name, image : image }); }; } function parseTextures(model) { var images = model.gltf.images; var textures = model.gltf.textures; for (var name in textures) { if (textures.hasOwnProperty(name)) { ++model._loadResources.pendingTextureLoads; var texture = textures[name]; var uri = new Uri(images[texture.source].uri); var imagePath = uri.resolve(model._baseUri).toString(); loadImage(imagePath).then(imageLoad(model, name)).otherwise(getFailedLoadFunction(model, 'image', imagePath)); } } } var nodeAxisScratch = new Cartesian3(); var nodeTranslationScratch = new Cartesian3(); var nodeQuaternionScratch = new Quaternion(); var nodeScaleScratch = new Cartesian3(); function getTransform(node) { if (defined(node.matrix)) { return Matrix4.fromArray(node.matrix); } var axis = Cartesian3.fromArray(node.rotation, 0, nodeAxisScratch); return Matrix4.fromTranslationQuaternionRotationScale( Cartesian3.fromArray(node.translation, 0, nodeTranslationScratch), Quaternion.fromAxisAngle(axis, node.rotation[3], nodeQuaternionScratch), Cartesian3.fromArray(node.scale, 0 , nodeScaleScratch)); } function parseNodes(model) { var runtimeNodes = {}; var runtimeNodesByName = {}; var skinnedNodes = []; var skinnedNodesNames = model._loadResources.skinnedNodesNames; var nodes = model.gltf.nodes; for (var name in nodes) { if (nodes.hasOwnProperty(name)) { var node = nodes[name]; var runtimeNode = { // Animation targets matrix : undefined, translation : undefined, rotation : undefined, scale : undefined, // Per-node show inherited from parent computedShow : true, // Computed transforms transformToRoot : new Matrix4(), computedMatrix : new Matrix4(), dirtyNumber : 0, // The frame this node was made dirty by an animation; for graph traversal // Rendering commands : [], // empty for transform, light, and camera nodes // Skinned node inverseBindMatrices : undefined, // undefined when node is not skinned bindShapeMatrix : undefined, // undefined when node is not skinned or identity joints : [], // empty when node is not skinned computedJointMatrices : [], // empty when node is not skinned // Joint node jointName : node.jointName, // undefined when node is not a joint // Graph pointers children : [], // empty for leaf nodes parents : [], // empty for root nodes // Publicly-accessible ModelNode instance to modify animation targets publicNode : undefined }; runtimeNode.publicNode = new ModelNode(model, node, runtimeNode, name, getTransform(node)); runtimeNodes[name] = runtimeNode; runtimeNodesByName[node.name] = runtimeNode; if (defined(node.instanceSkin)) { skinnedNodesNames.push(name); skinnedNodes.push(runtimeNode); } } } model._runtime.nodes = runtimeNodes; model._runtime.nodesByName = runtimeNodesByName; model._runtime.skinnedNodes = skinnedNodes; } function parseMaterials(model) { var runtimeMaterials = {}; var runtimeMaterialsById = {}; var materials = model.gltf.materials; var uniformMaps = model._uniformMaps; for (var name in materials) { if (materials.hasOwnProperty(name)) { // Allocated now so ModelMaterial can keep a reference to it. uniformMaps[name] = { uniformMap : undefined, values : undefined, jointMatrixUniformName : undefined }; var material = materials[name]; var modelMaterial = new ModelMaterial(model, material, name); runtimeMaterials[material.name] = modelMaterial; runtimeMaterialsById[name] = modelMaterial; } } model._runtime.materialsByName = runtimeMaterials; model._runtime.materialsById = runtimeMaterialsById; } function parseMeshes(model) { var runtimeMeshes = {}; var runtimeMaterialsById = model._runtime.materialsById; var meshes = model.gltf.meshes; for (var name in meshes) { if (meshes.hasOwnProperty(name)) { var mesh = meshes[name]; runtimeMeshes[mesh.name] = new ModelMesh(mesh, runtimeMaterialsById, name); } } model._runtime.meshesByName = runtimeMeshes; } function parse(model) { if (!model._loadRendererResourcesFromCache) { parseBuffers(model); parseBufferViews(model); parseShaders(model); parsePrograms(model); parseTextures(model); } parseMaterials(model); parseMeshes(model); parseNodes(model); } /////////////////////////////////////////////////////////////////////////// function createBuffers(model, context) { var loadResources = model._loadResources; if (loadResources.pendingBufferLoads !== 0) { return; } var raw; var bufferView; var bufferViews = model.gltf.bufferViews; var buffers = loadResources.buffers; var rendererBuffers = model._rendererResources.buffers; while (loadResources.buffersToCreate.length > 0) { var bufferViewName = loadResources.buffersToCreate.dequeue(); bufferView = bufferViews[bufferViewName]; if (bufferView.target === WebGLRenderingContext.ARRAY_BUFFER) { // Only ARRAY_BUFFER here. ELEMENT_ARRAY_BUFFER created below. raw = new Uint8Array(buffers[bufferView.buffer], bufferView.byteOffset, bufferView.byteLength); var vertexBuffer = context.createVertexBuffer(raw, BufferUsage.STATIC_DRAW); vertexBuffer.vertexArrayDestroyable = false; rendererBuffers[bufferViewName] = vertexBuffer; } // bufferViews referencing animations are ignored here and handled in createRuntimeAnimations. // bufferViews referencing skins are ignored here and handled in createSkins. } // The Cesium Renderer requires knowing the datatype for an index buffer // at creation type, which is not part of the glTF bufferview so loop // through glTF accessors to create the bufferview's index buffer. var accessors = model.gltf.accessors; for (var name in accessors) { if (accessors.hasOwnProperty(name)) { var accessor = accessors[name]; bufferView = bufferViews[accessor.bufferView]; if ((bufferView.target === WebGLRenderingContext.ELEMENT_ARRAY_BUFFER) && !defined(rendererBuffers[accessor.bufferView])) { raw = new Uint8Array(buffers[bufferView.buffer], bufferView.byteOffset, bufferView.byteLength); var indexBuffer = context.createIndexBuffer(raw, BufferUsage.STATIC_DRAW, accessor.componentType); indexBuffer.vertexArrayDestroyable = false; rendererBuffers[accessor.bufferView] = indexBuffer; // In theory, several glTF accessors with different componentTypes could // point to the same glTF bufferView, which would break this. // In practice, it is unlikely as it will be UNSIGNED_SHORT. } } } } function createAttributeLocations(attributes) { var attributeLocations = {}; var length = attributes.length; for (var i = 0; i < length; ++i) { attributeLocations[attributes[i]] = i; } return attributeLocations; } function createProgram(name, model, context) { var programs = model.gltf.programs; var shaders = model._loadResources.shaders; var program = programs[name]; var attributeLocations = createAttributeLocations(program.attributes); var vs = shaders[program.vertexShader]; var fs = shaders[program.fragmentShader]; model._rendererResources.programs[name] = context.createShaderProgram(vs, fs, attributeLocations); if (model.allowPicking) { // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181 var pickFS = new ShaderSource({ sources : [fs], pickColorQualifier : 'uniform' }); model._rendererResources.pickPrograms[name] = context.createShaderProgram(vs, pickFS, attributeLocations); } } function createPrograms(model, context) { var loadResources = model._loadResources; var name; if (loadResources.pendingShaderLoads !== 0) { return; } if (model.asynchronous) { // Create one program per frame if (loadResources.programsToCreate.length > 0) { name = loadResources.programsToCreate.dequeue(); createProgram(name, model, context); } } else { // Create all loaded programs this frame while (loadResources.programsToCreate.length > 0) { name = loadResources.programsToCreate.dequeue(); createProgram(name, model, context); } } } function createSamplers(model, context) { var loadResources = model._loadResources; if (loadResources.createSamplers) { loadResources.createSamplers = false; var rendererSamplers = model._rendererResources.samplers; var samplers = model.gltf.samplers; for (var name in samplers) { if (samplers.hasOwnProperty(name)) { var sampler = samplers[name]; rendererSamplers[name] = context.createSampler({ wrapS : sampler.wrapS, wrapT : sampler.wrapT, minificationFilter : sampler.minFilter, magnificationFilter : sampler.magFilter }); } } } } function createTexture(gltfTexture, model, context) { var textures = model.gltf.textures; var texture = textures[gltfTexture.name]; var rendererSamplers = model._rendererResources.samplers; var sampler = rendererSamplers[texture.sampler]; var mipmap = (sampler.minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) || (sampler.minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) || (sampler.minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) || (sampler.minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR); var requiresNpot = mipmap || (sampler.wrapS === TextureWrap.REPEAT) || (sampler.wrapS === TextureWrap.MIRRORED_REPEAT) || (sampler.wrapT === TextureWrap.REPEAT) || (sampler.wrapT === TextureWrap.MIRRORED_REPEAT); var source = gltfTexture.image; var npot = !CesiumMath.isPowerOfTwo(source.width) || !CesiumMath.isPowerOfTwo(source.height); if (requiresNpot && npot) { // WebGL requires power-of-two texture dimensions for mipmapping and REPEAT/MIRRORED_REPEAT wrap modes. var canvas = document.createElement('canvas'); canvas.width = CesiumMath.nextPowerOfTwo(source.width); canvas.height = CesiumMath.nextPowerOfTwo(source.height); var canvasContext = canvas.getContext('2d'); canvasContext.drawImage(source, 0, 0, source.width, source.height, 0, 0, canvas.width, canvas.height); source = canvas; } var tx; if (texture.target === WebGLRenderingContext.TEXTURE_2D) { tx = context.createTexture2D({ source : source, pixelFormat : texture.internalFormat, pixelDatatype : texture.type, flipY : false }); } // GLTF_SPEC: Support TEXTURE_CUBE_MAP. https://github.com/KhronosGroup/glTF/issues/40 if (mipmap) { tx.generateMipmap(); } tx.sampler = sampler; model._rendererResources.textures[gltfTexture.name] = tx; } function createTextures(model, context) { var loadResources = model._loadResources; var gltfTexture; if (model.asynchronous) { // Create one texture per frame if (loadResources.texturesToCreate.length > 0) { gltfTexture = loadResources.texturesToCreate.dequeue(); createTexture(gltfTexture, model, context); } } else { // Create all loaded textures this frame while (loadResources.texturesToCreate.length > 0) { gltfTexture = loadResources.texturesToCreate.dequeue(); createTexture(gltfTexture, model, context); } } } function getAttributeLocations(model, primitive) { var gltf = model.gltf; var programs = gltf.programs; var techniques = gltf.techniques; var materials = gltf.materials; // Retrieve the compiled shader program to assign index values to attributes var attributeLocations = {}; var technique = techniques[materials[primitive.material].instanceTechnique.technique]; var parameters = technique.parameters; var pass = technique.passes[technique.pass]; var instanceProgram = pass.instanceProgram; var attributes = instanceProgram.attributes; var programAttributeLocations = model._rendererResources.programs[instanceProgram.program].vertexAttributes; // Note: WebGL shader compiler may have optimized and removed some attributes from programAttributeLocations for (var location in programAttributeLocations){ if (programAttributeLocations.hasOwnProperty(location)) { var parameter = parameters[attributes[location]]; attributeLocations[parameter.semantic] = programAttributeLocations[location].index; } } return attributeLocations; } function searchForest(forest, jointName) { var length = forest.length; for (var i = 0; i < length; ++i) { var stack = [forest[i]]; // Push root node of tree while (stack.length > 0) { var n = stack.pop(); if (n.jointName === jointName) { return n; } var children = n.children; var childrenLength = children.length; for (var k = 0; k < childrenLength; ++k) { stack.push(children[k]); } } } // This should never happen; the skeleton should have a node for all joints in the skin. return undefined; } function createJoints(model, runtimeSkins) { var gltf = model.gltf; var skins = gltf.skins; var nodes = gltf.nodes; var runtimeNodes = model._runtime.nodes; var skinnedNodesNames = model._loadResources.skinnedNodesNames; var length = skinnedNodesNames.length; for (var j = 0; j < length; ++j) { var name = skinnedNodesNames[j]; var skinnedNode = runtimeNodes[name]; var instanceSkin = nodes[name].instanceSkin; var runtimeSkin = runtimeSkins[instanceSkin.skin]; skinnedNode.inverseBindMatrices = runtimeSkin.inverseBindMatrices; skinnedNode.bindShapeMatrix = runtimeSkin.bindShapeMatrix; // 1. Find nodes with the names in instanceSkin.skeletons (the node's skeletons) // 2. These nodes form the root nodes of the forest to search for each joint in skin.jointNames. This search uses jointName, not the node's name. var forest = []; var gltfSkeletons = instanceSkin.skeletons; var skeletonsLength = gltfSkeletons.length; for (var k = 0; k < skeletonsLength; ++k) { forest.push(runtimeNodes[gltfSkeletons[k]]); } var gltfJointNames = skins[instanceSkin.skin].jointNames; var jointNamesLength = gltfJointNames.length; for (var i = 0; i < jointNamesLength; ++i) { var jointName = gltfJointNames[i]; skinnedNode.joints.push(searchForest(forest, jointName)); } } } function createSkins(model) { var loadResources = model._loadResources; if (!loadResources.finishedBuffersCreation()) { return; } if (!loadResources.createSkins) { return; } loadResources.createSkins = false; var gltf = model.gltf; var accessors = gltf.accessors; var skins = gltf.skins; var runtimeSkins = {}; for (var name in skins) { if (skins.hasOwnProperty(name)) { var skin = skins[name]; var accessor = accessors[skin.inverseBindMatrices]; var bindShapeMatrix; if (!Matrix4.equals(skin.bindShapeMatrix, Matrix4.IDENTITY)) { bindShapeMatrix = Matrix4.clone(skin.bindShapeMatrix); } runtimeSkins[name] = { inverseBindMatrices : ModelAnimationCache.getSkinInverseBindMatrices(model, accessor), bindShapeMatrix : bindShapeMatrix // not used when undefined }; } } createJoints(model, runtimeSkins); } function getChannelEvaluator(model, runtimeNode, targetPath, spline) { return function(localAnimationTime) { // Workaround for https://github.com/KhronosGroup/glTF/issues/219 /* if (targetPath === 'translation') { return; } */ runtimeNode[targetPath] = spline.evaluate(localAnimationTime, runtimeNode[targetPath]); runtimeNode.dirtyNumber = model._maxDirtyNumber; }; } function createRuntimeAnimations(model) { var loadResources = model._loadResources; if (!loadResources.finishedPendingLoads()) { return; } if (!loadResources.createRuntimeAnimations) { return; } loadResources.createRuntimeAnimations = false; model._runtime.animations = { }; var runtimeNodes = model._runtime.nodes; var animations = model.gltf.animations; var accessors = model.gltf.accessors; var name; for (var animationName in animations) { if (animations.hasOwnProperty(animationName)) { var animation = animations[animationName]; var channels = animation.channels; var parameters = animation.parameters; var samplers = animation.samplers; var parameterValues = {}; for (name in parameters) { if (parameters.hasOwnProperty(name)) { parameterValues[name] = ModelAnimationCache.getAnimationParameterValues(model, accessors[parameters[name]]); } } // Find start and stop time for the entire animation var startTime = Number.MAX_VALUE; var stopTime = -Number.MAX_VALUE; var length = channels.length; var channelEvaluators = new Array(length); for (var i = 0; i < length; ++i) { var channel = channels[i]; var target = channel.target; var sampler = samplers[channel.sampler]; var times = parameterValues[sampler.input]; startTime = Math.min(startTime, times[0]); stopTime = Math.max(stopTime, times[times.length - 1]); var spline = ModelAnimationCache.getAnimationSpline(model, animationName, animation, channel.sampler, sampler, parameterValues); // GLTF_SPEC: Support more targets like materials. https://github.com/KhronosGroup/glTF/issues/142 channelEvaluators[i] = getChannelEvaluator(model, runtimeNodes[target.id], target.path, spline); } model._runtime.animations[animationName] = { startTime : startTime, stopTime : stopTime, channelEvaluators : channelEvaluators }; } } } function createVertexArrays(model, context) { var loadResources = model._loadResources; if (!loadResources.finishedBuffersCreation() || !loadResources.finishedProgramCreation()) { return; } if (!loadResources.createVertexArrays) { return; } loadResources.createVertexArrays = false; var rendererBuffers = model._rendererResources.buffers; var rendererVertexArrays = model._rendererResources.vertexArrays; var gltf = model.gltf; var accessors = gltf.accessors; var meshes = gltf.meshes; for (var meshName in meshes) { if (meshes.hasOwnProperty(meshName)) { var primitives = meshes[meshName].primitives; var primitivesLength = primitives.length; for (var i = 0; i < primitivesLength; ++i) { var primitive = primitives[i]; // GLTF_SPEC: This does not take into account attribute arrays, // indicated by when an attribute points to a parameter with a // count property. // // https://github.com/KhronosGroup/glTF/issues/258 var attributeLocations = getAttributeLocations(model, primitive); var attrs = []; var primitiveAttributes = primitive.attributes; for (var attrName in primitiveAttributes) { if (primitiveAttributes.hasOwnProperty(attrName)) { var attributeLocation = attributeLocations[attrName]; // Skip if the attribute is not used by the material, e.g., because the asset was exported // with an attribute that wasn't used and the asset wasn't optimized. if (defined(attributeLocation)) { var a = accessors[primitiveAttributes[attrName]]; attrs.push({ index : attributeLocation, vertexBuffer : rendererBuffers[a.bufferView], componentsPerAttribute : getModelAccessor(a).componentsPerAttribute, componentDatatype : a.componentType, normalize : false, offsetInBytes : a.byteOffset, strideInBytes : a.byteStride }); } } } var accessor = accessors[primitive.indices]; var indexBuffer = rendererBuffers[accessor.bufferView]; rendererVertexArrays[meshName + '.primitive.' + i] = context.createVertexArray(attrs, indexBuffer); } } } } function getBooleanStates(states) { // GLTF_SPEC: SAMPLE_ALPHA_TO_COVERAGE not used by Cesium var booleanStates = {}; booleanStates[WebGLRenderingContext.BLEND] = false; booleanStates[WebGLRenderingContext.CULL_FACE] = false; booleanStates[WebGLRenderingContext.DEPTH_TEST] = false; booleanStates[WebGLRenderingContext.POLYGON_OFFSET_FILL] = false; booleanStates[WebGLRenderingContext.SCISSOR_TEST] = false; var enable = states.enable; var length = enable.length; var i; for (i = 0; i < length; ++i) { booleanStates[enable[i]] = true; } return booleanStates; } function createRenderStates(model, context) { var loadResources = model._loadResources; if (loadResources.createRenderStates) { loadResources.createRenderStates = false; var rendererRenderStates = model._rendererResources.renderStates; var techniques = model.gltf.techniques; for (var name in techniques) { if (techniques.hasOwnProperty(name)) { var technique = techniques[name]; var pass = technique.passes[technique.pass]; var states = pass.states; var booleanStates = getBooleanStates(states); var statesFunctions = defaultValue(states.functions, defaultValue.EMPTY_OBJECT); var blendColor = defaultValue(statesFunctions.blendColor, [0.0, 0.0, 0.0, 0.0]); var blendEquationSeparate = defaultValue(statesFunctions.blendEquationSeparate, [ WebGLRenderingContext.FUNC_ADD, WebGLRenderingContext.FUNC_ADD]); var blendFuncSeparate = defaultValue(statesFunctions.blendFuncSeparate, [ WebGLRenderingContext.ONE, WebGLRenderingContext.ONE, WebGLRenderingContext.ZERO, WebGLRenderingContext.ZERO]); var colorMask = defaultValue(statesFunctions.colorMask, [true, true, true, true]); var depthRange = defaultValue(statesFunctions.depthRange, [0.0, 1.0]); var polygonOffset = defaultValue(statesFunctions.polygonOffset, [0.0, 0.0]); var scissor = defaultValue(statesFunctions.scissor, [0.0, 0.0, 0.0, 0.0]); rendererRenderStates[name] = context.createRenderState({ frontFace : defined(statesFunctions.frontFace) ? statesFunctions.frontFace[0] : WebGLRenderingContext.CCW, cull : { enabled : booleanStates[WebGLRenderingContext.CULL_FACE], face : defined(statesFunctions.cullFace) ? statesFunctions.cullFace[0] : WebGLRenderingContext.BACK }, lineWidth : defined(statesFunctions.lineWidth) ? statesFunctions.lineWidth[0] : 1.0, polygonOffset : { enabled : booleanStates[WebGLRenderingContext.POLYGON_OFFSET_FILL], factor : polygonOffset[0], units : polygonOffset[1] }, scissorTest : { enabled : booleanStates[WebGLRenderingContext.SCISSOR_TEST], rectangle : { x : scissor[0], y : scissor[1], width : scissor[2], height : scissor[3] } }, depthRange : { near : depthRange[0], far : depthRange[1] }, depthTest : { enabled : booleanStates[WebGLRenderingContext.DEPTH_TEST], func : defined(statesFunctions.depthFunc) ? statesFunctions.depthFunc[0] : WebGLRenderingContext.LESS }, colorMask : { red : colorMask[0], green : colorMask[1], blue : colorMask[2], alpha : colorMask[3] }, depthMask : defined(statesFunctions.depthMask) ? statesFunctions.depthMask[0] : true, blending : { enabled : booleanStates[WebGLRenderingContext.BLEND], color : { red : blendColor[0], green : blendColor[1], blue : blendColor[2], alpha : blendColor[3] }, equationRgb : blendEquationSeparate[0], equationAlpha : blendEquationSeparate[1], functionSourceRgb : blendFuncSeparate[0], functionSourceAlpha : blendFuncSeparate[1], functionDestinationRgb : blendFuncSeparate[2], functionDestinationAlpha : blendFuncSeparate[3] } }); } } } } // This doesn't support LOCAL, which we could add if it is ever used. var gltfSemanticUniforms = { MODEL : function(uniformState) { return function() { return uniformState.model; }; }, VIEW : function(uniformState) { return function() { return uniformState.view; }; }, PROJECTION : function(uniformState) { return function() { return uniformState.projection; }; }, MODELVIEW : function(uniformState) { return function() { return uniformState.modelView; }; }, MODELVIEWPROJECTION : function(uniformState) { return function() { return uniformState.modelViewProjection; }; }, MODELINVERSE : function(uniformState) { return function() { return uniformState.inverseModel; }; }, VIEWINVERSE : function(uniformState) { return function() { return uniformState.inverseView; }; }, PROJECTIONINVERSE : function(uniformState) { return function() { return uniformState.inverseProjection; }; }, MODELVIEWINVERSE : function(uniformState) { return function() { return uniformState.inverseModelView; }; }, MODELVIEWPROJECTIONINVERSE : function(uniformState) { return function() { return uniformState.inverseModelViewProjection; }; }, MODELINVERSETRANSPOSE : function(uniformState) { return function() { return uniformState.inverseTranposeModel; }; }, MODELVIEWINVERSETRANSPOSE : function(uniformState) { return function() { return uniformState.normal; }; }, VIEWPORT : function(uniformState) { return function() { return uniformState.viewportCartesian4; }; } // JOINTMATRIX created in createCommands() }; /////////////////////////////////////////////////////////////////////////// function getScalarUniformFunction(value, model) { var that = { value : value, clone : function(source, result) { return source; }, func : function() { return that.value; } }; return that; } function getVec2UniformFunction(value, model) { var that = { value : Cartesian2.fromArray(value), clone : Cartesian2.clone, func : function() { return that.value; } }; return that; } function getVec3UniformFunction(value, model) { var that = { value : Cartesian3.fromArray(value), clone : Cartesian3.clone, func : function() { return that.value; } }; return that; } function getVec4UniformFunction(value, model) { var that = { value : Cartesian4.fromArray(value), clone : Cartesian4.clone, func : function() { return that.value; } }; return that; } function getMat2UniformFunction(value, model) { var that = { value : Matrix2.fromColumnMajorArray(value), clone : Matrix2.clone, func : function() { return that.value; } }; return that; } function getMat3UniformFunction(value, model) { var that = { value : Matrix3.fromColumnMajorArray(value), clone : Matrix3.clone, func : function() { return that.value; } }; return that; } function getMat4UniformFunction(value, model) { var that = { value : Matrix4.fromColumnMajorArray(value), clone : Matrix4.clone, func : function() { return that.value; } }; return that; } function getTextureUniformFunction(value, model) { var that = { value : model._rendererResources.textures[value], clone : function(source, result) { return source; }, func : function() { return that.value; } }; return that; } var gltfUniformFunctions = {}; // this check must use typeof, not defined, because defined doesn't work with undeclared variables. if (typeof WebGLRenderingContext !== 'undefined') { gltfUniformFunctions[WebGLRenderingContext.FLOAT] = getScalarUniformFunction; gltfUniformFunctions[WebGLRenderingContext.FLOAT_VEC2] = getVec2UniformFunction; gltfUniformFunctions[WebGLRenderingContext.FLOAT_VEC3] = getVec3UniformFunction; gltfUniformFunctions[WebGLRenderingContext.FLOAT_VEC4] = getVec4UniformFunction; gltfUniformFunctions[WebGLRenderingContext.INT] = getScalarUniformFunction; gltfUniformFunctions[WebGLRenderingContext.INT_VEC2] = getVec2UniformFunction; gltfUniformFunctions[WebGLRenderingContext.INT_VEC3] = getVec3UniformFunction; gltfUniformFunctions[WebGLRenderingContext.INT_VEC4] = getVec4UniformFunction; gltfUniformFunctions[WebGLRenderingContext.BOOL] = getScalarUniformFunction; gltfUniformFunctions[WebGLRenderingContext.BOOL_VEC2] = getVec2UniformFunction; gltfUniformFunctions[WebGLRenderingContext.BOOL_VEC3] = getVec3UniformFunction; gltfUniformFunctions[WebGLRenderingContext.BOOL_VEC4] = getVec4UniformFunction; gltfUniformFunctions[WebGLRenderingContext.FLOAT_MAT2] = getMat2UniformFunction; gltfUniformFunctions[WebGLRenderingContext.FLOAT_MAT3] = getMat3UniformFunction; gltfUniformFunctions[WebGLRenderingContext.FLOAT_MAT4] = getMat4UniformFunction; gltfUniformFunctions[WebGLRenderingContext.SAMPLER_2D] = getTextureUniformFunction; // GLTF_SPEC: Support SAMPLER_CUBE. https://github.com/KhronosGroup/glTF/issues/40 } function getUniformFunctionFromSource(source, model) { var runtimeNode = model._runtime.nodes[source]; return function() { return runtimeNode.computedMatrix; }; } function createUniformMaps(model, context) { var loadResources = model._loadResources; if (!loadResources.finishedTextureCreation() || !loadResources.finishedProgramCreation()) { return; } if (!loadResources.createUniformMaps) { return; } loadResources.createUniformMaps = false; var gltf = model.gltf; var materials = gltf.materials; var techniques = gltf.techniques; var programs = gltf.programs; var uniformMaps = model._uniformMaps; for (var materialName in materials) { if (materials.hasOwnProperty(materialName)) { var material = materials[materialName]; var instanceTechnique = material.instanceTechnique; var instanceParameters = instanceTechnique.values; var technique = techniques[instanceTechnique.technique]; var parameters = technique.parameters; var pass = technique.passes[technique.pass]; var instanceProgram = pass.instanceProgram; var uniforms = instanceProgram.uniforms; var uniformMap = {}; var uniformValues = {}; var jointMatrixUniformName; // Uniform parameters for this pass for (var name in uniforms) { if (uniforms.hasOwnProperty(name)) { var parameterName = uniforms[name]; var parameter = parameters[parameterName]; // GLTF_SPEC: This does not take into account uniform arrays, // indicated by parameters with a count property. // // https://github.com/KhronosGroup/glTF/issues/258 // GLTF_SPEC: In this implementation, material parameters with a // semantic or targeted via a source (for animation) are not // targetable for material animations. Is this too strict? // // https://github.com/KhronosGroup/glTF/issues/142 if (defined(instanceParameters[parameterName])) { // Parameter overrides by the instance technique var uv = gltfUniformFunctions[parameter.type](instanceParameters[parameterName], model); uniformMap[name] = uv.func; uniformValues[parameterName] = uv; } else if (defined(parameter.semantic)) { if (parameter.semantic !== 'JOINTMATRIX') { // Map glTF semantic to Cesium automatic uniform uniformMap[name] = gltfSemanticUniforms[parameter.semantic](context.uniformState); } else { jointMatrixUniformName = name; } } else if (defined(parameter.source)) { uniformMap[name] = getUniformFunctionFromSource(parameter.source, model); } else if (defined(parameter.value)) { // Technique value that isn't overridden by a material var uv2 = gltfUniformFunctions[parameter.type](parameter.value, model); uniformMap[name] = uv2.func; uniformValues[parameterName] = uv2; } } } var u = uniformMaps[materialName]; u.uniformMap = uniformMap; // uniform name -> function for the renderer u.values = uniformValues; // material parameter name -> ModelMaterial for modifying the parameter at runtime u.jointMatrixUniformName = jointMatrixUniformName; } } } function createPickColorFunction(color) { return function() { return color; }; } function createJointMatricesFunction(runtimeNode) { return function() { return runtimeNode.computedJointMatrices; }; } function createCommand(model, gltfNode, runtimeNode, context) { var nodeCommands = model._nodeCommands; var pickIds = model._pickIds; var allowPicking = model.allowPicking; var runtimeMeshes = model._runtime.meshesByName; var debugShowBoundingVolume = model.debugShowBoundingVolume; var resources = model._rendererResources; var rendererVertexArrays = resources.vertexArrays; var rendererPrograms = resources.programs; var rendererPickPrograms = resources.pickPrograms; var rendererRenderStates = resources.renderStates; var uniformMaps = model._uniformMaps; var gltf = model.gltf; var accessors = gltf.accessors; var gltfMeshes = gltf.meshes; var techniques = gltf.techniques; var materials = gltf.materials; var meshes = defined(gltfNode.meshes) ? gltfNode.meshes : gltfNode.instanceSkin.meshes; var meshesLength = meshes.length; for (var j = 0; j < meshesLength; ++j) { var name = meshes[j]; var mesh = gltfMeshes[name]; var primitives = mesh.primitives; var length = primitives.length; // The glTF node hierarchy is a DAG so a node can have more than one // parent, so a node may already have commands. If so, append more // since they will have a different model matrix. for (var i = 0; i < length; ++i) { var primitive = primitives[i]; var ix = accessors[primitive.indices]; var instanceTechnique = materials[primitive.material].instanceTechnique; var technique = techniques[instanceTechnique.technique]; var pass = technique.passes[technique.pass]; var instanceProgram = pass.instanceProgram; var boundingSphere; var positionAttribute = primitive.attributes.POSITION; if (defined(positionAttribute)) { var a = accessors[positionAttribute]; boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.fromArray(a.min), Cartesian3.fromArray(a.max)); } var vertexArray = rendererVertexArrays[name + '.primitive.' + i]; var count = ix.count; var offset = (ix.byteOffset / IndexDatatype.getSizeInBytes(ix.componentType)); // glTF has offset in bytes. Cesium has offsets in indices var um = uniformMaps[primitive.material]; var uniformMap = um.uniformMap; if (defined(um.jointMatrixUniformName)) { var jointUniformMap = {}; jointUniformMap[um.jointMatrixUniformName] = createJointMatricesFunction(runtimeNode); uniformMap = combine(uniformMap, jointUniformMap); } var rs = rendererRenderStates[instanceTechnique.technique]; // GLTF_SPEC: Offical means to determine translucency. https://github.com/KhronosGroup/glTF/issues/105 var isTranslucent = rs.blending.enabled; var owner = { primitive : defaultValue(model.pickPrimitive, model), id : model.id, node : runtimeNode.publicNode, mesh : runtimeMeshes[mesh.name] }; var command = new DrawCommand({ boundingVolume : new BoundingSphere(), // updated in update() modelMatrix : new Matrix4(), // computed in update() primitiveType : primitive.primitive, vertexArray : vertexArray, count : count, offset : offset, shaderProgram : rendererPrograms[instanceProgram.program], uniformMap : uniformMap, renderState : rs, owner : owner, pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE }); var pickCommand; if (allowPicking) { var pickId = context.createPickId(owner); pickIds.push(pickId); var pickUniformMap = combine( uniformMap, { czm_pickColor : createPickColorFunction(pickId.color) }); pickCommand = new DrawCommand({ boundingVolume : new BoundingSphere(), // updated in update() modelMatrix : new Matrix4(), // computed in update() primitiveType : primitive.primitive, vertexArray : vertexArray, count : count, offset : offset, shaderProgram : rendererPickPrograms[instanceProgram.program], uniformMap : pickUniformMap, renderState : rs, owner : owner, pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE }); } var nodeCommand = { show : true, boundingSphere : boundingSphere, command : command, pickCommand : pickCommand }; runtimeNode.commands.push(nodeCommand); nodeCommands.push(nodeCommand); } } } function createRuntimeNodes(model, context) { var loadResources = model._loadResources; if (!loadResources.finishedPendingLoads() || !loadResources.finishedResourceCreation()) { return; } if (!loadResources.createRuntimeNodes) { return; } loadResources.createRuntimeNodes = false; var rootNodes = []; var runtimeNodes = model._runtime.nodes; var gltf = model.gltf; var nodes = gltf.nodes; var scene = gltf.scenes[gltf.scene]; var sceneNodes = scene.nodes; var length = sceneNodes.length; var stack = []; var axis = new Cartesian3(); for (var i = 0; i < length; ++i) { stack.push({ parentRuntimeNode : undefined, gltfNode : nodes[sceneNodes[i]], id : sceneNodes[i] }); while (stack.length > 0) { var n = stack.pop(); var parentRuntimeNode = n.parentRuntimeNode; var gltfNode = n.gltfNode; // Node hierarchy is a DAG so a node can have more than one parent so it may already exist var runtimeNode = runtimeNodes[n.id]; if (runtimeNode.parents.length === 0) { if (defined(gltfNode.matrix)) { runtimeNode.matrix = Matrix4.fromColumnMajorArray(gltfNode.matrix); } else { // TRS converted to Cesium types axis = Cartesian3.fromArray(gltfNode.rotation, 0, axis); runtimeNode.translation = Cartesian3.fromArray(gltfNode.translation); runtimeNode.rotation = Quaternion.fromAxisAngle(axis, gltfNode.rotation[3]); runtimeNode.scale = Cartesian3.fromArray(gltfNode.scale); } } if (defined(parentRuntimeNode)) { parentRuntimeNode.children.push(runtimeNode); runtimeNode.parents.push(parentRuntimeNode); } else { rootNodes.push(runtimeNode); } if (defined(gltfNode.meshes) || defined(gltfNode.instanceSkin)) { createCommand(model, gltfNode, runtimeNode, context); } var children = gltfNode.children; var childrenLength = children.length; for (var k = 0; k < childrenLength; ++k) { stack.push({ parentRuntimeNode : runtimeNode, gltfNode : nodes[children[k]], id : children[k] }); } } } model._runtime.rootNodes = rootNodes; model._runtime.nodes = runtimeNodes; } function createResources(model, context) { if (model._loadRendererResourcesFromCache) { var resources = model._rendererResources; var cachedResources = model._cachedRendererResources; resources.buffers = cachedResources.buffers; resources.vertexArrays = cachedResources.vertexArrays; resources.programs = cachedResources.programs; resources.pickPrograms = cachedResources.pickPrograms; resources.textures = cachedResources.textures; resources.samplers = cachedResources.samplers; resources.renderStates = cachedResources.renderStates; } else { createBuffers(model, context); // using glTF bufferViews createPrograms(model, context); createSamplers(model, context); createTextures(model, context); } createSkins(model); createRuntimeAnimations(model); if (!model._loadRendererResourcesFromCache) { createVertexArrays(model, context); // using glTF meshes createRenderStates(model, context); // using glTF materials/techniques/passes/states // Long-term, we might not cache render states if they could change // due to an animation, e.g., a uniform going from opaque to transparent. // Could use copy-on-write if it is worth it. Probably overkill. } createUniformMaps(model, context); // using glTF materials/techniques/passes/instanceProgram createRuntimeNodes(model, context); // using glTF scene } /////////////////////////////////////////////////////////////////////////// function getNodeMatrix(node, result) { var publicNode = node.publicNode; var publicMatrix = publicNode.matrix; if (publicNode.useMatrix && defined(publicMatrix)) { // Public matrix overrides orginial glTF matrix and glTF animations Matrix4.clone(publicMatrix, result); } else if (defined(node.matrix)) { Matrix4.clone(node.matrix, result); } else { Matrix4.fromTranslationQuaternionRotationScale(node.translation, node.rotation, node.scale, result); // Keep matrix returned by the node in-sync if the node is targeted by an animation. Only TRS nodes can be targeted. publicNode.setMatrix(result); } } var scratchNodeStack = []; function updateNodeHierarchyModelMatrix(model, modelTransformChanged, justLoaded) { var maxDirtyNumber = model._maxDirtyNumber; var allowPicking = model.allowPicking; var rootNodes = model._runtime.rootNodes; var length = rootNodes.length; var nodeStack = scratchNodeStack; var computedModelMatrix = model._computedModelMatrix; for (var i = 0; i < length; ++i) { var n = rootNodes[i]; getNodeMatrix(n, n.transformToRoot); nodeStack.push(n); while (nodeStack.length > 0) { n = nodeStack.pop(); var transformToRoot = n.transformToRoot; var commands = n.commands; if ((n.dirtyNumber === maxDirtyNumber) || modelTransformChanged || justLoaded) { var commandsLength = commands.length; if (commandsLength > 0) { // Node has meshes, which has primitives. Update their commands. for (var j = 0 ; j < commandsLength; ++j) { var primitiveCommand = commands[j]; var command = primitiveCommand.command; Matrix4.multiplyTransformation(computedModelMatrix, transformToRoot, command.modelMatrix); // PERFORMANCE_IDEA: Can use transformWithoutScale if no node up to the root has scale (inclug animation) BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume); if (allowPicking) { var pickCommand = primitiveCommand.pickCommand; Matrix4.clone(command.modelMatrix, pickCommand.modelMatrix); BoundingSphere.clone(command.boundingVolume, pickCommand.boundingVolume); } } } else { // Node has a light or camera n.computedMatrix = Matrix4.multiplyTransformation(computedModelMatrix, transformToRoot, n.computedMatrix); } } var children = n.children; var childrenLength = children.length; for (var k = 0; k < childrenLength; ++k) { var child = children[k]; // A node's transform needs to be updated if // - It was targeted for animation this frame, or // - Any of its ancestors were targeted for animation this frame // PERFORMANCE_IDEA: if a child has multiple parents and only one of the parents // is dirty, all the subtrees for each child instance will be dirty; we probably // won't see this in the wild often. child.dirtyNumber = Math.max(child.dirtyNumber, n.dirtyNumber); if ((child.dirtyNumber === maxDirtyNumber) || justLoaded) { // Don't check for modelTransformChanged since if only the model's model matrix changed, // we do not need to rebuild the local transform-to-root, only the final // [model's-model-matrix][transform-to-root] above. getNodeMatrix(child, child.transformToRoot); Matrix4.multiplyTransformation(transformToRoot, child.transformToRoot, child.transformToRoot); } nodeStack.push(child); } } } ++model._maxDirtyNumber; } var scratchObjectSpace = new Matrix4(); function applySkins(model) { var skinnedNodes = model._runtime.skinnedNodes; var length = skinnedNodes.length; for (var i = 0; i < length; ++i) { var node = skinnedNodes[i]; scratchObjectSpace = Matrix4.inverseTransformation(node.transformToRoot, scratchObjectSpace); var computedJointMatrices = node.computedJointMatrices; var joints = node.joints; var bindShapeMatrix = node.bindShapeMatrix; var inverseBindMatrices = node.inverseBindMatrices; var inverseBindMatricesLength = inverseBindMatrices.length; for (var m = 0; m < inverseBindMatricesLength; ++m) { // [joint-matrix] = [node-to-root^-1][joint-to-root][inverse-bind][bind-shape] if (!defined(computedJointMatrices[m])) { computedJointMatrices[m] = new Matrix4(); } computedJointMatrices[m] = Matrix4.multiplyTransformation(scratchObjectSpace, joints[m].transformToRoot, computedJointMatrices[m]); computedJointMatrices[m] = Matrix4.multiplyTransformation(computedJointMatrices[m], inverseBindMatrices[m], computedJointMatrices[m]); if (defined(bindShapeMatrix)) { // Optimization for when bind shape matrix is the identity. computedJointMatrices[m] = Matrix4.multiplyTransformation(computedJointMatrices[m], bindShapeMatrix, computedJointMatrices[m]); } } } } function updatePerNodeShow(model) { // Totally not worth it, but we could optimize this: // http://blogs.agi.com/insight3d/index.php/2008/02/13/deletion-in-bounding-volume-hierarchies/ var rootNodes = model._runtime.rootNodes; var length = rootNodes.length; var nodeStack = scratchNodeStack; for (var i = 0; i < length; ++i) { var n = rootNodes[i]; n.computedShow = n.publicNode.show; nodeStack.push(n); while (nodeStack.length > 0) { n = nodeStack.pop(); var show = n.computedShow; var nodeCommands = n.commands; var nodeCommandsLength = nodeCommands.length; for (var j = 0 ; j < nodeCommandsLength; ++j) { nodeCommands[j].show = show; } // if commandsLength is zero, the node has a light or camera var children = n.children; var childrenLength = children.length; for (var k = 0; k < childrenLength; ++k) { var child = children[k]; // Parent needs to be shown for child to be shown. child.computedShow = show && child.publicNode.show; nodeStack.push(child); } } } } function updatePickIds(model, context) { var id = model.id; if (model._id !== id) { model._id = id; var pickIds = model._pickIds; var length = pickIds.length; for (var i = 0; i < length; ++i) { pickIds[i].object.id = id; } } } function updateWireframe(model) { if (model._debugWireframe !== model.debugWireframe) { model._debugWireframe = model.debugWireframe; // This assumes the original primitive was TRIANGLES and that the triangles // are connected for the wireframe to look perfect. var primitiveType = model.debugWireframe ? PrimitiveType.LINES : PrimitiveType.TRIANGLES; var nodeCommands = model._nodeCommands; var length = nodeCommands.length; for (var i = 0; i < length; ++i) { nodeCommands[i].command.primitiveType = primitiveType; } } } function updateShowBoundingVolume(model) { if (model.debugShowBoundingVolume !== model._debugShowBoundingVolume) { model._debugShowBoundingVolume = model.debugShowBoundingVolume; var debugShowBoundingVolume = model.debugShowBoundingVolume; var nodeCommands = model._nodeCommands; var length = nodeCommands.length; for (var i = 0; i < length; i++) { nodeCommands[i].command.debugShowBoundingVolume = debugShowBoundingVolume; } } } var scratchDrawingBufferDimensions = new Cartesian2(); var scratchToCenter = new Cartesian3(); var scratchProj = new Cartesian3(); function scaleInPixels(positionWC, radius, context, frameState) { var camera = frameState.camera; var frustum = camera.frustum; var toCenter = Cartesian3.subtract(camera.positionWC, positionWC, scratchToCenter); var proj = Cartesian3.multiplyByScalar(camera.directionWC, Cartesian3.dot(toCenter, camera.directionWC), scratchProj); var distance = Math.max(frustum.near, Cartesian3.magnitude(proj) - radius); scratchDrawingBufferDimensions.x = context.drawingBufferWidth; scratchDrawingBufferDimensions.y = context.drawingBufferHeight; var pixelSize = frustum.getPixelSize(scratchDrawingBufferDimensions, distance); var pixelScale = Math.max(pixelSize.x, pixelSize.y); return pixelScale; } var scratchPosition = new Cartesian3(); function getScale(model, context, frameState) { var scale = model.scale; if (model.minimumPixelSize !== 0.0) { // Compute size of bounding sphere in pixels var maxPixelSize = Math.max(context.drawingBufferWidth, context.drawingBufferHeight); var m = model.modelMatrix; scratchPosition.x = m[12]; scratchPosition.y = m[13]; scratchPosition.z = m[14]; var radius = model.boundingSphere.radius; var metersPerPixel = scaleInPixels(scratchPosition, radius, context, frameState); // metersPerPixel is always > 0.0 var pixelsPerMeter = 1.0 / metersPerPixel; var diameterInPixels = Math.min(pixelsPerMeter * (2.0 * radius), maxPixelSize); // Maintain model's minimum pixel size if (diameterInPixels < model.minimumPixelSize) { scale = (model.minimumPixelSize * metersPerPixel) / (2.0 * model._initialRadius); } } return scale; } function releaseCachedGltf(model) { if (defined(model._cacheKey) && defined(model._cachedGltf) && (--model._cachedGltf.count === 0)) { delete gltfCache[model._cacheKey]; } model._cachedGltf = undefined; } /////////////////////////////////////////////////////////////////////////// var CachedRendererResources = function(context, cacheKey) { this.buffers = undefined; this.vertexArrays = undefined; this.programs = undefined; this.pickPrograms = undefined; this.textures = undefined; this.samplers = undefined; this.renderStates = undefined; this.ready = false; this.context = context; this.cacheKey = cacheKey; this.count = 0; }; function destroy(property) { for (var name in property) { if (property.hasOwnProperty(name)) { property[name].destroy(); } } } function destroyCachedRendererResources(resources) { destroy(resources.buffers); destroy(resources.vertexArrays); destroy(resources.programs); destroy(resources.pickPrograms); destroy(resources.textures); } CachedRendererResources.prototype.release = function() { if (--this.count === 0) { if (defined(this.cacheKey)) { // Remove if this was cached delete this.context.cache.modelRendererResourceCache[this.cacheKey]; } destroyCachedRendererResources(this); return destroyObject(this); } return undefined; }; /////////////////////////////////////////////////////////////////////////// /** * Called when {@link Viewer} or {@link CesiumWidget} render the scene to * get the draw commands needed to render this primitive. *

* Do not call this function directly. This is documented just to * list the exceptions that may be propagated when the scene is rendered: *

* * @exception {RuntimeError} Failed to load external reference. */ Model.prototype.update = function(context, frameState, commandList) { if (frameState.mode !== SceneMode.SCENE3D) { return; } if ((this._state === ModelState.NEEDS_LOAD) && defined(this.gltf)) { // Use renderer resources from cache instead of loading/creating them? var cachedRendererResources; var cacheKey = this.cacheKey; if (defined(cacheKey)) { context.cache.modelRendererResourceCache = defaultValue(context.cache.modelRendererResourceCache, {}); var modelCaches = context.cache.modelRendererResourceCache; cachedRendererResources = modelCaches[this.cacheKey]; if (defined(cachedRendererResources)) { if (!cachedRendererResources.ready) { // Cached resources for the model are not loaded yet. We'll // try again every frame until they are. return; } ++cachedRendererResources.count; this._loadRendererResourcesFromCache = true; } else { cachedRendererResources = new CachedRendererResources(context, cacheKey); cachedRendererResources.count = 1; modelCaches[this.cacheKey] = cachedRendererResources; } this._cachedRendererResources = cachedRendererResources; } else { cachedRendererResources = new CachedRendererResources(context); cachedRendererResources.count = 1; this._cachedRendererResources = cachedRendererResources; } this._state = ModelState.LOADING; this._boundingSphere = computeBoundingSphere(this.gltf); this._initialRadius = this._boundingSphere.radius; this._loadResources = new LoadResources(); parse(this); } var justLoaded = false; if (this._state === ModelState.FAILED) { throw this._loadError; } if (this._state === ModelState.LOADING) { // Incrementally create WebGL resources as buffers/shaders/textures are downloaded createResources(this, context); var loadResources = this._loadResources; if (loadResources.finishedPendingLoads() && loadResources.finishedResourceCreation()) { this._state = ModelState.LOADED; this._loadResources = undefined; // Clear CPU memory since WebGL resources were created. var resources = this._rendererResources; var cachedResources = this._cachedRendererResources; cachedResources.buffers = resources.buffers; cachedResources.vertexArrays = resources.vertexArrays; cachedResources.programs = resources.programs; cachedResources.pickPrograms = resources.pickPrograms; cachedResources.textures = resources.textures; cachedResources.samplers = resources.samplers; cachedResources.renderStates = resources.renderStates; cachedResources.ready = true; if (this.releaseGltfJson) { releaseCachedGltf(this); } justLoaded = true; } } var show = this.show && (this.scale !== 0.0); if ((show && this._state === ModelState.LOADED) || justLoaded) { var animated = this.activeAnimations.update(frameState) || this._cesiumAnimationsDirty; this._cesiumAnimationsDirty = false; // Model's model matrix needs to be updated var modelTransformChanged = !Matrix4.equals(this._modelMatrix, this.modelMatrix) || (this._scale !== this.scale) || (this._minimumPixelSize !== this.minimumPixelSize) || (this.minimumPixelSize !== 0.0); // Minimum pixel size changed or is enabled if (modelTransformChanged || justLoaded) { Matrix4.clone(this.modelMatrix, this._modelMatrix); this._scale = this.scale; this._minimumPixelSize = this.minimumPixelSize; var scale = getScale(this, context, frameState); var computedModelMatrix = this._computedModelMatrix; Matrix4.multiplyByUniformScale(this.modelMatrix, scale, computedModelMatrix); Matrix4.multiplyTransformation(computedModelMatrix, yUpToZUp, computedModelMatrix); } // Update modelMatrix throughout the graph as needed if (animated || modelTransformChanged || justLoaded) { updateNodeHierarchyModelMatrix(this, modelTransformChanged, justLoaded); if (animated || justLoaded) { // Apply skins if animation changed any node transforms applySkins(this); } } if (this._perNodeShowDirty) { this._perNodeShowDirty = false; updatePerNodeShow(this); } updatePickIds(this, context); updateWireframe(this); updateShowBoundingVolume(this); } if (justLoaded) { // Called after modelMatrix update. var model = this; frameState.afterRender.push(function() { model._ready = true; model.readyToRender.raiseEvent(model); }); return; } // We don't check show at the top of the function since we // want to be able to progressively load models when they are not shown, // and then have them visible immediately when show is set to true. if (show) { // PERFORMANCE_IDEA: This is terrible var passes = frameState.passes; var nodeCommands = this._nodeCommands; var length = nodeCommands.length; var i; var nc; if (passes.render) { for (i = 0; i < length; ++i) { nc = nodeCommands[i]; if (nc.show) { commandList.push(nc.command); } } } if (passes.pick) { for (i = 0; i < length; ++i) { nc = nodeCommands[i]; if (nc.show) { commandList.push(nc.pickCommand); } } } } }; /** * Returns true if this object was destroyed; otherwise, false. *

* If this object was destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. * * @returns {Boolean} true if this object was destroyed; otherwise, false. * * @see Model#destroy */ Model.prototype.isDestroyed = function() { return false; }; /** * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic * release of WebGL resources, instead of relying on the garbage collector to destroy this object. *

* Once an object is destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. Therefore, * assign the return value (undefined) to the object as done in the example. * * @returns {undefined} * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * @see Model#isDestroyed * * @example * model = model && model.destroy(); */ Model.prototype.destroy = function() { this._rendererResources = undefined; this._cachedRendererResources = this._cachedRendererResources && this._cachedRendererResources.release(); var pickIds = this._pickIds; var length = pickIds.length; for (var i = 0; i < length; ++i) { pickIds[i].destroy(); } releaseCachedGltf(this); return destroyObject(this); }; return Model; });