import arraySlice from '../Core/arraySlice.js'; import BoundingSphere from '../Core/BoundingSphere.js'; import Cartesian3 from '../Core/Cartesian3.js'; import Cartesian4 from '../Core/Cartesian4.js'; import Color from '../Core/Color.js'; import combine from '../Core/combine.js'; import ComponentDatatype from '../Core/ComponentDatatype.js'; import defaultValue from '../Core/defaultValue.js'; import defined from '../Core/defined.js'; import defineProperties from '../Core/defineProperties.js'; import destroyObject from '../Core/destroyObject.js'; import DeveloperError from '../Core/DeveloperError.js'; import FeatureDetection from '../Core/FeatureDetection.js'; import IndexDatatype from '../Core/IndexDatatype.js'; import Matrix4 from '../Core/Matrix4.js'; import PrimitiveType from '../Core/PrimitiveType.js'; import RuntimeError from '../Core/RuntimeError.js'; import Transforms from '../Core/Transforms.js'; import WebGLConstants from '../Core/WebGLConstants.js'; import addDefaults from '../ThirdParty/GltfPipeline/addDefaults.js'; import ForEach from '../ThirdParty/GltfPipeline/ForEach.js'; import getAccessorByteStride from '../ThirdParty/GltfPipeline/getAccessorByteStride.js'; import numberOfComponentsForType from '../ThirdParty/GltfPipeline/numberOfComponentsForType.js'; import parseGlb from '../ThirdParty/GltfPipeline/parseGlb.js'; import updateVersion from '../ThirdParty/GltfPipeline/updateVersion.js'; import when from '../ThirdParty/when.js'; import Axis from './Axis.js'; import ModelLoadResources from './ModelLoadResources.js'; import ModelUtility from './ModelUtility.js'; import processModelMaterialsCommon from './processModelMaterialsCommon.js'; import processPbrMaterials from './processPbrMaterials.js'; import SceneMode from './SceneMode.js'; import Vector3DTileBatch from './Vector3DTileBatch.js'; import Vector3DTilePrimitive from './Vector3DTilePrimitive.js'; var boundingSphereCartesian3Scratch = new Cartesian3(); var ModelState = ModelUtility.ModelState; /////////////////////////////////////////////////////////////////////////// /** * A 3D model for classifying other 3D assets based on glTF, the runtime 3D asset format. * 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 {ArrayBuffer|Uint8Array} options.gltf A binary glTF buffer. * @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 {RuntimeError} Only binary glTF is supported. * @exception {RuntimeError} Buffer data must be embedded in the binary glTF. * @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) { // Parse and update binary glTF gltf = parseGlb(gltf); updateVersion(gltf); addDefaults(gltf); processModelMaterialsCommon(gltf); processPbrMaterials(gltf); } else { throw new RuntimeError('Only binary glTF is supported as a classifier.'); } ForEach.buffer(gltf, function(buffer) { if (!defined(buffer.extras._pipeline.source)) { throw new RuntimeError('Buffer data must be embedded in the binary gltf.'); } }); 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; /** * 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. *

* 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._debugShowBoundingVolume = 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._classificationType = options.classificationType; // Undocumented options this._vertexShaderLoaded = options.vertexShaderLoaded; this._classificationShaderLoaded = options.classificationShaderLoaded; this._uniformMapLoaded = options.uniformMapLoaded; this._pickIdLoaded = options.pickIdLoaded; 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._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 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 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 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. *

* This promise is resolved at the end of the frame before the first frame the model is rendered in. *

* * @memberof ClassificationModel.prototype * @type {Promise.} * @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; } } }); /////////////////////////////////////////////////////////////////////////// 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 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 }; } function getAttributeLocations() { return { POSITION : 0, _BATCHID : 1 }; } function createVertexArray(model) { var loadResources = model._loadResources; if (!loadResources.finishedBuffersCreation() || 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 = {}; ForEach.meshPrimitiveAttribute(primitive, function(accessorId, 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. var attributeLocation = attributeLocations[attributeName]; if (defined(attributeLocation)) { var a = accessors[accessorId]; 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 uniformMap = {}; ForEach.technique(model.gltf, function(technique) { ForEach.techniqueUniform(technique, function(uniform, uniformName) { if (!defined(uniform.semantic) || !defined(gltfSemanticUniforms[uniform.semantic])) { return; } uniformMap[uniformName] = gltfSemanticUniforms[uniform.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 pickId = defined(model._pickIdLoaded) ? model._pickIdLoaded() : undefined; 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, _uniformMap : uniformMap, _pickId : pickId, _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._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 (!FeatureDetection.supportsWebP.initialized) { FeatureDetection.supportsWebP.initialize(); return; } var supportsWebP = FeatureDetection.supportsWebP(); 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(); ModelUtility.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, supportsWebP); addBuffersToLoadResources(this); parseBufferViews(this); this._boundingSphere = ModelUtility.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); }; export default ClassificationModel;