UNPKG

cesium

Version:

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

1,216 lines (1,049 loc) 47.7 kB
define([ '../Core/arraySlice', '../Core/BoundingSphere', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Cartographic', '../Core/Color', '../Core/combine', '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', '../Core/FeatureDetection', '../Core/IndexDatatype', '../Core/Matrix4', '../Core/PrimitiveType', '../Core/Quaternion', '../Core/Resource', '../Core/RuntimeError', '../Core/Transforms', '../Core/WebGLConstants', '../ThirdParty/GltfPipeline/ForEach', '../ThirdParty/GltfPipeline/getAccessorByteStride', '../ThirdParty/GltfPipeline/numberOfComponentsForType', '../ThirdParty/GltfPipeline/parseBinaryGltf', '../ThirdParty/when', './Axis', './ClassificationType', './ModelLoadResources', './ModelUtility', './SceneMode', './Vector3DTileBatch', './Vector3DTilePrimitive' ], function( arraySlice, BoundingSphere, Cartesian3, Cartesian4, Cartographic, Color, combine, ComponentDatatype, defaultValue, defined, defineProperties, destroyObject, DeveloperError, FeatureDetection, IndexDatatype, Matrix4, PrimitiveType, Quaternion, Resource, RuntimeError, Transforms, WebGLConstants, ForEach, getAccessorByteStride, numberOfComponentsForType, parseBinaryGltf, when, Axis, ClassificationType, ModelLoadResources, ModelUtility, SceneMode, Vector3DTileBatch, Vector3DTilePrimitive) { 'use strict'; // Bail out if the browser doesn't support typed arrays, to prevent the setup function // from failing, since we won't be able to create a WebGL context anyway. if (!FeatureDetection.supportsTypedArrays()) { return {}; } var boundingSphereCartesian3Scratch = new Cartesian3(); var ModelState = { NEEDS_LOAD : 0, LOADING : 1, LOADED : 2, FAILED : 3 }; /////////////////////////////////////////////////////////////////////////// /** * A 3D model for classifying other 3D assets based on glTF, the runtime asset format for WebGL, OpenGL ES, and OpenGL. * This is a special case when a model of a 3D tileset becomes a classifier when setting {@link Cesium3DTileset#classificationType}. * * @alias ClassificationModel * @constructor * * @private * * @param {Object} [options] Object with the following properties: * @param {Object|ArrayBuffer|Uint8Array} options.gltf The object for the glTF JSON or an arraybuffer of Binary glTF defined by the KHR_binary_glTF extension. * @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 {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 {ClassificationType} [options.classificationType] What this model will classify. * * @exception {DeveloperError} bgltf is not a valid Binary glTF file. * @exception {DeveloperError} Only glTF Binary version 1 is supported. * @exception {RuntimeError} Only binary glTF is supported. * @exception {RuntimeError} Only one node is supported for classification and it must have a mesh. * @exception {RuntimeError} Only one mesh is supported when using b3dm for classification. * @exception {RuntimeError} Only one primitive per mesh is supported when using b3dm for classification. * @exception {RuntimeError} The mesh must have a position attribute. * @exception {RuntimeError} The mesh must have a batch id attribute. */ function ClassificationModel(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var gltf = options.gltf; if (gltf instanceof ArrayBuffer) { gltf = new Uint8Array(gltf); } if (gltf instanceof Uint8Array) { // Binary glTF gltf = parseBinaryGltf(gltf); } else { throw new RuntimeError('Only binary glTF is supported as a classifier.'); } var gltfNodes = gltf.nodes; var gltfMeshes = gltf.meshes; var gltfNode = gltfNodes[0]; var meshId = gltfNode.mesh; if (gltfNodes.length !== 1 || !defined(meshId)) { throw new RuntimeError('Only one node is supported for classification and it must have a mesh.'); } if (gltfMeshes.length !== 1) { throw new RuntimeError('Only one mesh is supported when using b3dm for classification.'); } var gltfPrimitives = gltfMeshes[0].primitives; if (gltfPrimitives.length !== 1) { throw new RuntimeError('Only one primitive per mesh is supported when using b3dm for classification.'); } var gltfPositionAttribute = gltfPrimitives[0].attributes.POSITION; if (!defined(gltfPositionAttribute)) { throw new RuntimeError('The mesh must have a position attribute.'); } var gltfBatchIdAttribute = gltfPrimitives[0].attributes._BATCHID; if (!defined(gltfBatchIdAttribute)) { throw new RuntimeError('The mesh must have a batch id attribute.'); } this._gltf = gltf; var basePath = defaultValue(options.basePath, ''); this._resource = Resource.createIfNeeded(basePath); /** * 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); this._ready = false; this._readyPromise = when.defer(); /** * 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._classificationType = options.classificationType; // Undocumented options this._vertexShaderLoaded = options.vertexShaderLoaded; this._classificationShaderLoaded = options.classificationShaderLoaded; this._uniformMapLoaded = options.uniformMapLoaded; this._pickVertexShaderLoaded = options.pickVertexShaderLoaded; this._pickFragmentShaderLoaded = options.pickFragmentShaderLoaded; this._pickUniformMapLoaded = options.pickUniformMapLoaded; this._ignoreCommands = defaultValue(options.ignoreCommands, false); this._upAxis = defaultValue(options.upAxis, Axis.Y); this._batchTable = options.batchTable; this._computedModelMatrix = new Matrix4(); // Derived from modelMatrix and axis 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._dirty = false; // true when the model was transformed this frame this._nodeMatrix = new Matrix4(); this._primitive = undefined; this._extensionsUsed = undefined; // Cached used glTF extensions this._extensionsRequired = undefined; // Cached required glTF extensions this._quantizedUniforms = undefined; // Quantized uniforms for WEB3D_quantized_attributes this._buffers = {}; this._vertexArray = undefined; this._shaderProgram = undefined; this._pickShaderProgram = undefined; this._uniformMap = undefined; this._geometryByteLength = 0; this._trianglesLength = 0; // 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 } defineProperties(ClassificationModel.prototype, { /** * The object for the glTF JSON, including properties with default values omitted * from the JSON provided to this model. * * @memberof ClassificationModel.prototype * * @type {Object} * @readonly * * @default undefined */ gltf : { get : function() { return this._gltf; } }, /** * 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. * * @memberof ClassificationModel.prototype * * @type {String} * @readonly * * @default '' */ basePath : { get : function() { return this._resource.url; } }, /** * The model's bounding sphere in its local coordinate system. * * @memberof ClassificationModel.prototype * * @type {BoundingSphere} * @readonly * * @default undefined * * @exception {DeveloperError} The model is not loaded. Use ClassificationModel.readyPromise or wait for ClassificationModel.ready to be true. * * @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. Use ClassificationModel.readyPromise or wait for ClassificationModel.ready to be true.'); } //>>includeEnd('debug'); var modelMatrix = this.modelMatrix; var nonUniformScale = Matrix4.getScale(modelMatrix, boundingSphereCartesian3Scratch); var 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 ClassificationModel#readyPromise} is resolved. * * @memberof ClassificationModel.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 ClassificationModel.prototype * @type {Promise.<ClassificationModel>} * @readonly * * @see ClassificationModel#ready */ readyPromise : { get : function() { return this._readyPromise.promise; } }, /** * Returns true if the model was transformed this frame * * @memberof ClassificationModel.prototype * * @type {Boolean} * @readonly * * @private */ dirty : { get : function() { return this._dirty; } }, /** * Returns an object with all of the glTF extensions used. * * @memberof ClassificationModel.prototype * * @type {Object} * @readonly */ extensionsUsed : { get : function() { if (!defined(this._extensionsUsed)) { this._extensionsUsed = ModelUtility.getUsedExtensions(this.gltf); } return this._extensionsUsed; } }, /** * Returns an object with all of the glTF extensions required. * * @memberof ClassificationModel.prototype * * @type {Object} * @readonly */ extensionsRequired : { get : function() { if (!defined(this._extensionsRequired)) { this._extensionsRequired = ModelUtility.getRequiredExtensions(this.gltf); } 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 ClassificationModel.prototype * * @type {Number} * @default Axis.Y * @readonly * * @private */ upAxis : { get : function() { return this._upAxis; } }, /** * Gets the model's triangle count. * * @private */ trianglesLength : { get : function() { return this._trianglesLength; } }, /** * 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 0; } }, /** * Gets the model's classification type. * @memberof ClassificationModel.prototype * @type {ClassificationType} */ classificationType : { get : function() { return this._classificationType; } } }); var aMinScratch = new Cartesian3(); var aMaxScratch = new Cartesian3(); function computeBoundingSphere(model) { var gltf = model.gltf; var gltfNodes = gltf.nodes; var gltfMeshes = gltf.meshes; var min = new Cartesian3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); var max = new Cartesian3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); var n = gltfNodes[0]; var meshId = n.mesh; var transformToRoot = ModelUtility.getTransform(n); var mesh = gltfMeshes[meshId]; var primitive = mesh.primitives[0]; var positionAccessor = primitive.attributes.POSITION; var minMax = ModelUtility.getAccessorMinMax(gltf, positionAccessor); var aMin = Cartesian3.fromArray(minMax.min, 0, aMinScratch); var aMax = Cartesian3.fromArray(minMax.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 boundingSphere = BoundingSphere.fromCornerPoints(min, max); if (model._upAxis === Axis.Y) { BoundingSphere.transformWithoutScale(boundingSphere, Axis.Y_UP_TO_Z_UP, boundingSphere); } else if (model._upAxis === Axis.X) { BoundingSphere.transformWithoutScale(boundingSphere, Axis.X_UP_TO_Z_UP, boundingSphere); } return boundingSphere; } /////////////////////////////////////////////////////////////////////////// function getFailedLoadFunction(model, type, path) { return function() { model._state = ModelState.FAILED; model._readyPromise.reject(new RuntimeError('Failed to load ' + type + ': ' + path)); }; } function addBuffersToLoadResources(model) { var gltf = model.gltf; var loadResources = model._loadResources; ForEach.buffer(gltf, function(buffer, id) { loadResources.buffers[id] = buffer.extras._pipeline.source; }); } function bufferLoad(model, id) { return function(arrayBuffer) { var loadResources = model._loadResources; var buffer = new Uint8Array(arrayBuffer); --loadResources.pendingBufferLoads; model.gltf.buffers[id].extras._pipeline.source = buffer; }; } function parseBuffers(model) { var loadResources = model._loadResources; // Iterate this way for compatibility with objects and arrays var buffers = model.gltf.buffers; var length = buffers.length; for (var i = 0; i < length; ++i) { var buffer = buffers[i]; buffer.extras = defaultValue(buffer.extras, {}); buffer.extras._pipeline = defaultValue(buffer.extras._pipeline, {}); if (defined(buffer.extras._pipeline.source)) { loadResources.buffers[i] = buffer.extras._pipeline.source; } else { var bufferResource = model._resource.getDerivedResource({ url : buffer.uri }); ++loadResources.pendingBufferLoads; bufferResource.fetchArrayBuffer().then(bufferLoad(model, i)).otherwise(getFailedLoadFunction(model, 'buffer', bufferResource.uri)); } } } function parseBufferViews(model) { var bufferViews = model.gltf.bufferViews; var vertexBuffersToCreate = model._loadResources.vertexBuffersToCreate; // Only ARRAY_BUFFER here. ELEMENT_ARRAY_BUFFER created below. ForEach.bufferView(model.gltf, function(bufferView, id) { if (bufferView.target === WebGLConstants.ARRAY_BUFFER) { vertexBuffersToCreate.enqueue(id); } }); var indexBuffersToCreate = model._loadResources.indexBuffersToCreate; var indexBufferIds = {}; // 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. ForEach.accessor(model.gltf, function(accessor) { var bufferViewId = accessor.bufferView; var bufferView = bufferViews[bufferViewId]; if ((bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER) && !defined(indexBufferIds[bufferViewId])) { indexBufferIds[bufferViewId] = true; indexBuffersToCreate.enqueue({ id : bufferViewId, componentType : accessor.componentType }); } }); } function createVertexBuffer(bufferViewId, model) { var loadResources = model._loadResources; var bufferViews = model.gltf.bufferViews; var bufferView = bufferViews[bufferViewId]; var vertexBuffer = loadResources.getBuffer(bufferView); model._buffers[bufferViewId] = vertexBuffer; model._geometryByteLength += vertexBuffer.byteLength; } function createIndexBuffer(bufferViewId, componentType, model) { var loadResources = model._loadResources; var bufferViews = model.gltf.bufferViews; var bufferView = bufferViews[bufferViewId]; var indexBuffer = { typedArray : loadResources.getBuffer(bufferView), indexDatatype : componentType }; model._buffers[bufferViewId] = indexBuffer; model._geometryByteLength += indexBuffer.typedArray.byteLength; } function createBuffers(model) { var loadResources = model._loadResources; if (loadResources.pendingBufferLoads !== 0) { return; } var vertexBuffersToCreate = loadResources.vertexBuffersToCreate; var indexBuffersToCreate = loadResources.indexBuffersToCreate; while (vertexBuffersToCreate.length > 0) { createVertexBuffer(vertexBuffersToCreate.dequeue(), model); } while (indexBuffersToCreate.length > 0) { var i = indexBuffersToCreate.dequeue(); createIndexBuffer(i.id, i.componentType, model); } } function modifyShaderForQuantizedAttributes(shader, model) { var primitive = model.gltf.meshes[0].primitives[0]; var result = ModelUtility.modifyShaderForQuantizedAttributes(model.gltf, primitive, shader); model._quantizedUniforms = result.uniforms; return result.shader; } function modifyShader(shader, callback) { if (defined(callback)) { shader = callback(shader); } return shader; } function createProgram(model) { var gltf = model.gltf; var positionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'POSITION'); var batchIdName = ModelUtility.getAttributeOrUniformBySemantic(gltf, '_BATCHID'); var attributeLocations = {}; attributeLocations[positionName] = 0; attributeLocations[batchIdName] = 1; var modelViewProjectionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'MODELVIEWPROJECTION'); var uniformDecl; var toClip; if (!defined(modelViewProjectionName)) { var projectionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'PROJECTION'); var modelViewName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'MODELVIEW'); if (!defined(modelViewName)) { modelViewName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'CESIUM_RTC_MODELVIEW'); } uniformDecl = 'uniform mat4 ' + modelViewName + ';\n' + 'uniform mat4 ' + projectionName + ';\n'; toClip = projectionName + ' * ' + modelViewName + ' * vec4(' + positionName + ', 1.0)'; } else { uniformDecl = 'uniform mat4 ' + modelViewProjectionName + ';\n'; toClip = modelViewProjectionName + ' * vec4(' + positionName + ', 1.0)'; } var computePosition = ' vec4 positionInClipCoords = ' + toClip + ';\n'; var vs = 'attribute vec3 ' + positionName + ';\n' + 'attribute float ' + batchIdName + ';\n' + uniformDecl + 'void main() {\n' + computePosition + ' gl_Position = czm_depthClampFarPlane(positionInClipCoords);\n' + '}\n'; var fs = '#ifdef GL_EXT_frag_depth\n' + '#extension GL_EXT_frag_depth : enable\n' + '#endif\n' + 'void main() \n' + '{ \n' + ' gl_FragColor = vec4(1.0); \n' + ' czm_writeDepthClampedToFarPlane();\n' + '}\n'; if (model.extensionsUsed.WEB3D_quantized_attributes) { vs = modifyShaderForQuantizedAttributes(vs, model); } var drawVS = modifyShader(vs, model._vertexShaderLoaded); var drawFS = modifyShader(fs, model._classificationShaderLoaded); drawVS = ModelUtility.modifyVertexShaderForLogDepth(drawVS, toClip); drawFS = ModelUtility.modifyFragmentShaderForLogDepth(drawFS); model._shaderProgram = { vertexShaderSource : drawVS, fragmentShaderSource : drawFS, attributeLocations : attributeLocations }; // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181 var pickVS = modifyShader(vs, model._pickVertexShaderLoaded); var pickFS = modifyShader(fs, model._pickFragmentShaderLoaded); pickVS = ModelUtility.modifyVertexShaderForLogDepth(pickVS, toClip); pickFS = ModelUtility.modifyFragmentShaderForLogDepth(pickFS); model._pickShaderProgram = { vertexShaderSource : pickVS, fragmentShaderSource : pickFS, attributeLocations : attributeLocations }; } function getAttributeLocations() { return { POSITION : 0, _BATCHID : 1 }; } function createVertexArray(model) { var loadResources = model._loadResources; if (!loadResources.finishedBuffersCreation()) { return; } if (defined(model._vertexArray)) { return; } var rendererBuffers = model._buffers; var gltf = model.gltf; var accessors = gltf.accessors; var meshes = gltf.meshes; var primitives = meshes[0].primitives; var primitive = primitives[0]; var attributeLocations = getAttributeLocations(); var attributes = {}; var primitiveAttributes = primitive.attributes; for (var attributeName in primitiveAttributes) { if (primitiveAttributes.hasOwnProperty(attributeName)) { var attributeLocation = attributeLocations[attributeName]; // 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[attributeName]]; attributes[attributeName] = { index : attributeLocation, vertexBuffer : rendererBuffers[a.bufferView], componentsPerAttribute : numberOfComponentsForType(a.type), componentDatatype : a.componentType, offsetInBytes : a.byteOffset, strideInBytes : getAccessorByteStride(gltf, a) }; } } } var indexBuffer; if (defined(primitive.indices)) { var accessor = accessors[primitive.indices]; indexBuffer = rendererBuffers[accessor.bufferView]; } model._vertexArray = { attributes : attributes, indexBuffer : indexBuffer }; } var gltfSemanticUniforms = { PROJECTION : function(uniformState, model) { return ModelUtility.getGltfSemanticUniforms().PROJECTION(uniformState, model); }, MODELVIEW : function(uniformState, model) { return ModelUtility.getGltfSemanticUniforms().MODELVIEW(uniformState, model); }, CESIUM_RTC_MODELVIEW : function(uniformState, model) { return ModelUtility.getGltfSemanticUniforms().CESIUM_RTC_MODELVIEW(uniformState, model); }, MODELVIEWPROJECTION : function(uniformState, model) { return ModelUtility.getGltfSemanticUniforms().MODELVIEWPROJECTION(uniformState, model); } }; function createUniformMap(model, context) { if (defined(model._uniformMap)) { return; } var techniques = model.gltf.techniques; var technique = techniques[0]; var parameters = technique.parameters; var uniforms = technique.uniforms; var uniformMap = {}; for (var name in uniforms) { if (uniforms.hasOwnProperty(name) && name !== 'extras') { var parameterName = uniforms[name]; var parameter = parameters[parameterName]; if (!defined(parameter.semantic) || !defined(gltfSemanticUniforms[parameter.semantic])) { continue; } uniformMap[name] = gltfSemanticUniforms[parameter.semantic](context.uniformState, model); } } model._uniformMap = uniformMap; } function createUniformsForQuantizedAttributes(model, primitive) { return ModelUtility.createUniformsForQuantizedAttributes(model.gltf, primitive, model._quantizedUniforms); } function triangleCountFromPrimitiveIndices(primitive, indicesCount) { switch (primitive.mode) { case PrimitiveType.TRIANGLES: return (indicesCount / 3); case PrimitiveType.TRIANGLE_STRIP: case PrimitiveType.TRIANGLE_FAN: return Math.max(indicesCount - 2, 0); default: return 0; } } function createPrimitive(model) { var batchTable = model._batchTable; var uniformMap = model._uniformMap; var vertexArray = model._vertexArray; var gltf = model.gltf; var accessors = gltf.accessors; var gltfMeshes = gltf.meshes; var primitive = gltfMeshes[0].primitives[0]; var ix = accessors[primitive.indices]; var positionAccessor = primitive.attributes.POSITION; var minMax = ModelUtility.getAccessorMinMax(gltf, positionAccessor); var boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.fromArray(minMax.min), Cartesian3.fromArray(minMax.max)); var offset; var count; if (defined(ix)) { count = ix.count; offset = (ix.byteOffset / IndexDatatype.getSizeInBytes(ix.componentType)); // glTF has offset in bytes. Cesium has offsets in indices } else { var positions = accessors[primitive.attributes.POSITION]; count = positions.count; offset = 0; } // Update model triangle count using number of indices model._trianglesLength += triangleCountFromPrimitiveIndices(primitive, count); // Allow callback to modify the uniformMap if (defined(model._uniformMapLoaded)) { uniformMap = model._uniformMapLoaded(uniformMap); } // Add uniforms for decoding quantized attributes if used if (model.extensionsUsed.WEB3D_quantized_attributes) { var quantizedUniformMap = createUniformsForQuantizedAttributes(model, primitive); uniformMap = combine(uniformMap, quantizedUniformMap); } var attribute = vertexArray.attributes.POSITION; var componentDatatype = attribute.componentDatatype; var typedArray = attribute.vertexBuffer; var byteOffset = typedArray.byteOffset; var bufferLength = typedArray.byteLength / ComponentDatatype.getSizeInBytes(componentDatatype); var positionsBuffer = ComponentDatatype.createArrayBufferView(componentDatatype, typedArray.buffer, byteOffset, bufferLength); attribute = vertexArray.attributes._BATCHID; componentDatatype = attribute.componentDatatype; typedArray = attribute.vertexBuffer; byteOffset = typedArray.byteOffset; bufferLength = typedArray.byteLength / ComponentDatatype.getSizeInBytes(componentDatatype); var vertexBatchIds = ComponentDatatype.createArrayBufferView(componentDatatype, typedArray.buffer, byteOffset, bufferLength); var buffer = vertexArray.indexBuffer.typedArray; var indices; if (vertexArray.indexBuffer.indexDatatype === IndexDatatype.UNSIGNED_SHORT) { indices = new Uint16Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Uint16Array.BYTES_PER_ELEMENT); } else { indices = new Uint32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Uint32Array.BYTES_PER_ELEMENT); } positionsBuffer = arraySlice(positionsBuffer); vertexBatchIds = arraySlice(vertexBatchIds); indices = arraySlice(indices, offset, offset + count); var batchIds = []; var indexCounts = []; var indexOffsets = []; var batchedIndices = []; var currentId = vertexBatchIds[indices[0]]; batchIds.push(currentId); indexOffsets.push(0); var batchId; var indexOffset; var indexCount; var indicesLength = indices.length; for (var j = 1; j < indicesLength; ++j) { batchId = vertexBatchIds[indices[j]]; if (batchId !== currentId) { indexOffset = indexOffsets[indexOffsets.length - 1]; indexCount = j - indexOffset; batchIds.push(batchId); indexCounts.push(indexCount); indexOffsets.push(j); batchedIndices.push(new Vector3DTileBatch({ offset : indexOffset, count : indexCount, batchIds : [currentId], color : Color.WHITE })); currentId = batchId; } } indexOffset = indexOffsets[indexOffsets.length - 1]; indexCount = indicesLength - indexOffset; indexCounts.push(indexCount); batchedIndices.push(new Vector3DTileBatch({ offset : indexOffset, count : indexCount, batchIds : [currentId], color : Color.WHITE })); var shader = model._shaderProgram; var vertexShaderSource = shader.vertexShaderSource; var fragmentShaderSource = shader.fragmentShaderSource; var attributeLocations = shader.attributeLocations; var pickUniformMap; var pickShader = model._pickShaderProgram; var pickVertexShaderSource = pickShader.vertexShaderSource; var pickFragmentShaderSource = pickShader.fragmentShaderSource; if (defined(model._pickUniformMapLoaded)) { pickUniformMap = model._pickUniformMapLoaded(uniformMap); } else { // This is unlikely, but could happen if the override shader does not // need new uniforms since, for example, its pick ids are coming from // a vertex attribute or are baked into the shader source. pickUniformMap = combine(uniformMap); } model._primitive = new Vector3DTilePrimitive({ classificationType : model._classificationType, positions : positionsBuffer, indices : indices, indexOffsets : indexOffsets, indexCounts : indexCounts, batchIds : batchIds, vertexBatchIds : vertexBatchIds, batchedIndices : batchedIndices, batchTable : batchTable, boundingVolume : new BoundingSphere(), // updated in update() _vertexShaderSource : vertexShaderSource, _fragmentShaderSource : fragmentShaderSource, _attributeLocations : attributeLocations, _pickVertexShaderSource : pickVertexShaderSource, _pickFragmentShaderSource : pickFragmentShaderSource, _uniformMap : uniformMap, _pickUniformMap : pickUniformMap, _modelMatrix : new Matrix4(), // updated in update() _boundingSphere : boundingSphere // used to update boundingVolume }); // Release CPU resources model._buffers = undefined; model._vertexArray = undefined; model._shaderProgram = undefined; model._pickShaderProgram = undefined; model._uniformMap = undefined; } function createRuntimeNodes(model) { var loadResources = model._loadResources; if (!loadResources.finished()) { return; } if (defined(model._primitive)) { return; } var gltf = model.gltf; var nodes = gltf.nodes; var gltfNode = nodes[0]; model._nodeMatrix = ModelUtility.getTransform(gltfNode, model._nodeMatrix); createPrimitive(model); } function createResources(model, frameState) { var context = frameState.context; ModelUtility.checkSupportedGlExtensions(model.gltf.glExtensionsUsed, context); createBuffers(model); // using glTF bufferViews createProgram(model); createVertexArray(model); // using glTF meshes createUniformMap(model, context); // using glTF materials/techniques createRuntimeNodes(model); // using glTF scene } /////////////////////////////////////////////////////////////////////////// var scratchComputedTranslation = new Cartesian4(); var scratchComputedMatrixIn2D = new Matrix4(); function updateNodeModelMatrix(model, modelTransformChanged, justLoaded, projection) { var computedModelMatrix = model._computedModelMatrix; if ((model._mode !== SceneMode.SCENE3D) && !model._ignoreCommands) { var translation = Matrix4.getColumn(computedModelMatrix, 3, scratchComputedTranslation); if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) { computedModelMatrix = Transforms.basisTo2D(projection, computedModelMatrix, scratchComputedMatrixIn2D); model._rtcCenter = model._rtcCenter3D; } else { var center = model.boundingSphere.center; var to2D = Transforms.wgs84To2DModelMatrix(projection, center, scratchComputedMatrixIn2D); computedModelMatrix = Matrix4.multiply(to2D, computedModelMatrix, scratchComputedMatrixIn2D); if (defined(model._rtcCenter)) { Matrix4.setTranslation(computedModelMatrix, Cartesian4.UNIT_W, computedModelMatrix); model._rtcCenter = model._rtcCenter2D; } } } var primitive = model._primitive; if (modelTransformChanged || justLoaded) { Matrix4.multiplyTransformation(computedModelMatrix, model._nodeMatrix, primitive._modelMatrix); BoundingSphere.transform(primitive._boundingSphere, primitive._modelMatrix, primitive._boundingVolume); if (defined(model._rtcCenter)) { Cartesian3.add(model._rtcCenter, primitive._boundingVolume.center, primitive._boundingVolume.center); } } } /////////////////////////////////////////////////////////////////////////// ClassificationModel.prototype.updateCommands = function(batchId, color) { this._primitive.updateCommands(batchId, color); }; ClassificationModel.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { return; } if ((this._state === ModelState.NEEDS_LOAD) && defined(this.gltf)) { this._state = ModelState.LOADING; if (this._state !== ModelState.FAILED) { var extensions = this.gltf.extensions; if (defined(extensions) && defined(extensions.CESIUM_RTC)) { var center = Cartesian3.fromArray(extensions.CESIUM_RTC.center); if (!Cartesian3.equals(center, Cartesian3.ZERO)) { this._rtcCenter3D = center; var projection = frameState.mapProjection; var ellipsoid = projection.ellipsoid; var cartographic = ellipsoid.cartesianToCartographic(this._rtcCenter3D); var projectedCart = projection.project(cartographic); Cartesian3.fromElements(projectedCart.z, projectedCart.x, projectedCart.y, projectedCart); this._rtcCenter2D = projectedCart; this._rtcCenterEye = new Cartesian3(); this._rtcCenter = this._rtcCenter3D; } } this._loadResources = new ModelLoadResources(); parseBuffers(this); } } var loadResources = this._loadResources; var justLoaded = false; if (this._state === ModelState.LOADING) { // Transition from LOADING -> LOADED once resources are downloaded and created. // Textures may continue to stream in while in the LOADED state. if (loadResources.pendingBufferLoads === 0) { ModelUtility.checkSupportedExtensions(this.extensionsRequired); addBuffersToLoadResources(this); parseBufferViews(this); this._boundingSphere = computeBoundingSphere(this); this._initialRadius = this._boundingSphere.radius; createResources(this, frameState); } if (loadResources.finished()) { this._state = ModelState.LOADED; justLoaded = true; } } if (defined(loadResources) && (this._state === ModelState.LOADED)) { if (!justLoaded) { createResources(this, frameState); } if (loadResources.finished()) { this._loadResources = undefined; // Clear CPU memory since WebGL resources were created. } } var show = this.show; if ((show && this._state === ModelState.LOADED) || justLoaded) { this._dirty = false; var modelMatrix = this.modelMatrix; var modeChanged = frameState.mode !== this._mode; this._mode = frameState.mode; // ClassificationModel's model matrix needs to be updated var modelTransformChanged = !Matrix4.equals(this._modelMatrix, modelMatrix) || modeChanged; if (modelTransformChanged || justLoaded) { Matrix4.clone(modelMatrix, this._modelMatrix); var computedModelMatrix = this._computedModelMatrix; Matrix4.clone(modelMatrix, computedModelMatrix); if (this._upAxis === Axis.Y) { Matrix4.multiplyTransformation(computedModelMatrix, Axis.Y_UP_TO_Z_UP, computedModelMatrix); } else if (this._upAxis === Axis.X) { Matrix4.multiplyTransformation(computedModelMatrix, Axis.X_UP_TO_Z_UP, computedModelMatrix); } } // Update modelMatrix throughout the graph as needed if (modelTransformChanged || justLoaded) { updateNodeModelMatrix(this, modelTransformChanged, justLoaded, frameState.mapProjection); this._dirty = true; } } if (justLoaded) { // Called after modelMatrix update. var model = this; frameState.afterRender.push(function() { model._ready = true; model._readyPromise.resolve(model); }); return; } if (show && !this._ignoreCommands) { this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; this._primitive.debugWireframe = this.debugWireframe; this._primitive.update(frameState); } }; ClassificationModel.prototype.isDestroyed = function() { return false; }; ClassificationModel.prototype.destroy = function() { this._primitive = this._primitive && this._primitive.destroy(); return destroyObject(this); }; return ClassificationModel; });